feat: preference framework qs changes (#5527)

* feat: query service changes base setup for preferences

* feat: added handlers for user and org preferences

* chore: added base for all user and all org preferences

* feat: added handlers for all user and all org preferences

* feat: register the preference routes and initDB in pkg/query-service

* feat: code refactor

* chore: too much fun code refactor

* chore: little little missing attributes

* fix: handle range queries better

* fix: handle range queries better

* chore: address review comments

* chore: use struct inheritance for the all preferences struct

* chore: address review comments

* chore: address review comments

* chore: correct preference routes

* chore: low hanging optimisations

* chore: address review comments

* chore: address review comments

* chore: added extra validations for the check in allowed values

* fix: better handling for the jwt claims

* fix: better handling for the jwt claims

* chore: move the error to preference apis

* chore: move the error to preference apis

* fix: move the 401 logic to the auth middleware
This commit is contained in:
Vikrant Gupta 2024-07-29 09:51:18 +05:30 committed by GitHub
parent 4360cd0397
commit d00024b64a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 763 additions and 2 deletions

View File

@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
@ -27,6 +28,7 @@ import (
"go.signoz.io/signoz/ee/query-service/integrations/gateway"
"go.signoz.io/signoz/ee/query-service/interfaces"
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/model"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
licensepkg "go.signoz.io/signoz/ee/query-service/license"
@ -40,6 +42,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
"go.signoz.io/signoz/pkg/query-service/app/opamp"
opAmpModel "go.signoz.io/signoz/pkg/query-service/app/opamp/model"
"go.signoz.io/signoz/pkg/query-service/app/preferences"
"go.signoz.io/signoz/pkg/query-service/cache"
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/healthcheck"
@ -109,6 +112,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
baseexplorer.InitWithDSN(baseconst.RELATIONAL_DATASOURCE_PATH)
if err := preferences.InitDB(baseconst.RELATIONAL_DATASOURCE_PATH); err != nil {
return nil, err
}
localDB, err := dashboards.InitDB(baseconst.RELATIONAL_DATASOURCE_PATH)
if err != nil {
@ -319,7 +326,17 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, e
// add auth middleware
getUserFromRequest := func(r *http.Request) (*basemodel.UserPayload, error) {
return auth.GetUserFromRequest(r, apiHandler)
user, err := auth.GetUserFromRequest(r, apiHandler)
if err != nil {
return nil, err
}
if user.User.OrgId == "" {
return nil, model.UnauthorizedError(errors.New("orgId is missing in the claims"))
}
return user, nil
}
am := baseapp.NewAuthMiddleware(getUserFromRequest)

View File

@ -29,12 +29,14 @@ import (
logsv3 "go.signoz.io/signoz/pkg/query-service/app/logs/v3"
"go.signoz.io/signoz/pkg/query-service/app/metrics"
metricsv3 "go.signoz.io/signoz/pkg/query-service/app/metrics/v3"
"go.signoz.io/signoz/pkg/query-service/app/preferences"
"go.signoz.io/signoz/pkg/query-service/app/querier"
querierV2 "go.signoz.io/signoz/pkg/query-service/app/querier/v2"
"go.signoz.io/signoz/pkg/query-service/app/queryBuilder"
tracesV3 "go.signoz.io/signoz/pkg/query-service/app/traces/v3"
"go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/cache"
"go.signoz.io/signoz/pkg/query-service/common"
"go.signoz.io/signoz/pkg/query-service/constants"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/query-service/postprocess"
@ -398,6 +400,22 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *AuthMiddleware) {
router.HandleFunc("/api/v1/disks", am.ViewAccess(aH.getDisks)).Methods(http.MethodGet)
// === Preference APIs ===
// user actions
router.HandleFunc("/api/v1/user/preferences", am.ViewAccess(aH.getAllUserPreferences)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/user/preferences/{preferenceId}", am.ViewAccess(aH.getUserPreference)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/user/preferences/{preferenceId}", am.ViewAccess(aH.updateUserPreference)).Methods(http.MethodPut)
// org actions
router.HandleFunc("/api/v1/org/preferences", am.AdminAccess(aH.getAllOrgPreferences)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/org/preferences/{preferenceId}", am.AdminAccess(aH.getOrgPreference)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/org/preferences/{preferenceId}", am.AdminAccess(aH.updateOrgPreference)).Methods(http.MethodPut)
// === Authentication APIs ===
router.HandleFunc("/api/v1/invite", am.AdminAccess(aH.inviteUser)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/invite/{token}", am.OpenAccess(aH.getInvite)).Methods(http.MethodGet)
@ -2192,6 +2210,115 @@ func (aH *APIHandler) WriteJSON(w http.ResponseWriter, r *http.Request, response
w.Write(resp)
}
// Preferences
func (ah *APIHandler) getUserPreference(
w http.ResponseWriter, r *http.Request,
) {
preferenceId := mux.Vars(r)["preferenceId"]
user := common.GetUserFromContext(r.Context())
preference, apiErr := preferences.GetUserPreference(
r.Context(), preferenceId, user.User.OrgId, user.User.Id,
)
if apiErr != nil {
RespondError(w, apiErr, nil)
return
}
ah.Respond(w, preference)
}
func (ah *APIHandler) updateUserPreference(
w http.ResponseWriter, r *http.Request,
) {
preferenceId := mux.Vars(r)["preferenceId"]
user := common.GetUserFromContext(r.Context())
req := preferences.UpdatePreference{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
RespondError(w, model.BadRequest(err), nil)
return
}
preference, apiErr := preferences.UpdateUserPreference(r.Context(), preferenceId, req.PreferenceValue, user.User.Id)
if apiErr != nil {
RespondError(w, apiErr, nil)
return
}
ah.Respond(w, preference)
}
func (ah *APIHandler) getAllUserPreferences(
w http.ResponseWriter, r *http.Request,
) {
user := common.GetUserFromContext(r.Context())
preference, apiErr := preferences.GetAllUserPreferences(
r.Context(), user.User.OrgId, user.User.Id,
)
if apiErr != nil {
RespondError(w, apiErr, nil)
return
}
ah.Respond(w, preference)
}
func (ah *APIHandler) getOrgPreference(
w http.ResponseWriter, r *http.Request,
) {
preferenceId := mux.Vars(r)["preferenceId"]
user := common.GetUserFromContext(r.Context())
preference, apiErr := preferences.GetOrgPreference(
r.Context(), preferenceId, user.User.OrgId,
)
if apiErr != nil {
RespondError(w, apiErr, nil)
return
}
ah.Respond(w, preference)
}
func (ah *APIHandler) updateOrgPreference(
w http.ResponseWriter, r *http.Request,
) {
preferenceId := mux.Vars(r)["preferenceId"]
req := preferences.UpdatePreference{}
user := common.GetUserFromContext(r.Context())
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
RespondError(w, model.BadRequest(err), nil)
return
}
preference, apiErr := preferences.UpdateOrgPreference(r.Context(), preferenceId, req.PreferenceValue, user.User.OrgId)
if apiErr != nil {
RespondError(w, apiErr, nil)
return
}
ah.Respond(w, preference)
}
func (ah *APIHandler) getAllOrgPreferences(
w http.ResponseWriter, r *http.Request,
) {
user := common.GetUserFromContext(r.Context())
preference, apiErr := preferences.GetAllOrgPreferences(
r.Context(), user.User.OrgId,
)
if apiErr != nil {
RespondError(w, apiErr, nil)
return
}
ah.Respond(w, preference)
}
// Integrations
func (ah *APIHandler) RegisterIntegrationRoutes(router *mux.Router, am *AuthMiddleware) {
subRouter := router.PathPrefix("/api/v1/integrations").Subrouter()

View File

@ -0,0 +1,37 @@
package preferences
var preferenceMap = map[string]Preference{
"DASHBOARDS_LIST_VIEW": {
Key: "DASHBOARDS_LIST_VIEW",
Name: "Dashboards List View",
Description: "",
ValueType: "string",
DefaultValue: "grid",
AllowedValues: []interface{}{"grid", "list"},
IsDiscreteValues: true,
AllowedScopes: []string{"user", "org"},
},
"LOGS_TOOLBAR_COLLAPSED": {
Key: "LOGS_TOOLBAR_COLLAPSED",
Name: "Logs toolbar",
Description: "",
ValueType: "boolean",
DefaultValue: false,
AllowedValues: []interface{}{true, false},
IsDiscreteValues: true,
AllowedScopes: []string{"user", "org"},
},
"MAX_DEPTH_ALLOWED": {
Key: "MAX_DEPTH_ALLOWED",
Name: "Max Depth Allowed",
Description: "",
ValueType: "integer",
DefaultValue: 10,
IsDiscreteValues: false,
Range: Range{
Min: 0,
Max: 100,
},
AllowedScopes: []string{"user", "org"},
},
}

View File

@ -0,0 +1,544 @@
package preferences
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/jmoiron/sqlx"
"go.signoz.io/signoz/ee/query-service/model"
)
type Range struct {
Min int64 `json:"min"`
Max int64 `json:"max"`
}
type Preference struct {
Key string `json:"key"`
Name string `json:"name"`
Description string `json:"description"`
ValueType string `json:"valueType"`
DefaultValue interface{} `json:"defaultValue"`
AllowedValues []interface{} `json:"allowedValues"`
IsDiscreteValues bool `json:"isDiscreteValues"`
Range Range `json:"range"`
AllowedScopes []string `json:"allowedScopes"`
}
func (p *Preference) ErrorValueTypeMismatch() *model.ApiError {
return &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("the preference value is not of expected type: %s", p.ValueType)}
}
const (
PreferenceValueTypeInteger string = "integer"
PreferenceValueTypeFloat string = "float"
PreferenceValueTypeString string = "string"
PreferenceValueTypeBoolean string = "boolean"
)
const (
OrgAllowedScope string = "org"
UserAllowedScope string = "user"
)
func (p *Preference) checkIfInAllowedValues(preferenceValue interface{}) (bool, *model.ApiError) {
switch p.ValueType {
case PreferenceValueTypeInteger:
_, ok := preferenceValue.(int64)
if !ok {
return false, p.ErrorValueTypeMismatch()
}
case PreferenceValueTypeFloat:
_, ok := preferenceValue.(float64)
if !ok {
return false, p.ErrorValueTypeMismatch()
}
case PreferenceValueTypeString:
_, ok := preferenceValue.(string)
if !ok {
return false, p.ErrorValueTypeMismatch()
}
case PreferenceValueTypeBoolean:
_, ok := preferenceValue.(bool)
if !ok {
return false, p.ErrorValueTypeMismatch()
}
}
isInAllowedValues := false
for _, value := range p.AllowedValues {
switch p.ValueType {
case PreferenceValueTypeInteger:
allowedValue, ok := value.(int64)
if !ok {
return false, p.ErrorValueTypeMismatch()
}
if allowedValue == preferenceValue {
isInAllowedValues = true
}
case PreferenceValueTypeFloat:
allowedValue, ok := value.(float64)
if !ok {
return false, p.ErrorValueTypeMismatch()
}
if allowedValue == preferenceValue {
isInAllowedValues = true
}
case PreferenceValueTypeString:
allowedValue, ok := value.(string)
if !ok {
return false, p.ErrorValueTypeMismatch()
}
if allowedValue == preferenceValue {
isInAllowedValues = true
}
case PreferenceValueTypeBoolean:
allowedValue, ok := value.(bool)
if !ok {
return false, p.ErrorValueTypeMismatch()
}
if allowedValue == preferenceValue {
isInAllowedValues = true
}
}
}
return isInAllowedValues, nil
}
func (p *Preference) IsValidValue(preferenceValue interface{}) *model.ApiError {
typeSafeValue := preferenceValue
switch p.ValueType {
case PreferenceValueTypeInteger:
val, ok := preferenceValue.(int64)
if !ok {
floatVal, ok := preferenceValue.(float64)
if !ok || floatVal != float64(int64(floatVal)) {
return p.ErrorValueTypeMismatch()
}
val = int64(floatVal)
typeSafeValue = val
}
if !p.IsDiscreteValues {
if val < p.Range.Min || val > p.Range.Max {
return &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("the preference value is not in the range specified, min: %v , max:%v", p.Range.Min, p.Range.Max)}
}
}
case PreferenceValueTypeString:
_, ok := preferenceValue.(string)
if !ok {
return p.ErrorValueTypeMismatch()
}
case PreferenceValueTypeFloat:
_, ok := preferenceValue.(float64)
if !ok {
return p.ErrorValueTypeMismatch()
}
case PreferenceValueTypeBoolean:
_, ok := preferenceValue.(bool)
if !ok {
return p.ErrorValueTypeMismatch()
}
}
// check the validity of the value being part of allowed values or the range specified if any
if p.IsDiscreteValues {
if p.AllowedValues != nil {
isInAllowedValues, valueMisMatchErr := p.checkIfInAllowedValues(typeSafeValue)
if valueMisMatchErr != nil {
return valueMisMatchErr
}
if !isInAllowedValues {
return &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("the preference value is not in the list of allowedValues: %v", p.AllowedValues)}
}
}
}
return nil
}
func (p *Preference) IsEnabledForScope(scope string) bool {
isPreferenceEnabledForGivenScope := false
if p.AllowedScopes != nil {
for _, allowedScope := range p.AllowedScopes {
if allowedScope == strings.ToLower(scope) {
isPreferenceEnabledForGivenScope = true
}
}
}
return isPreferenceEnabledForGivenScope
}
func (p *Preference) SanitizeValue(preferenceValue interface{}) interface{} {
switch p.ValueType {
case PreferenceValueTypeBoolean:
if preferenceValue == "1" || preferenceValue == true {
return true
} else {
return false
}
default:
return preferenceValue
}
}
type AllPreferences struct {
Preference
Value interface{} `json:"value"`
}
type PreferenceKV struct {
PreferenceId string `json:"preference_id" db:"preference_id"`
PreferenceValue interface{} `json:"preference_value" db:"preference_value"`
}
type UpdatePreference struct {
PreferenceValue interface{} `json:"preference_value"`
}
var db *sqlx.DB
func InitDB(datasourceName string) error {
var err error
db, err = sqlx.Open("sqlite3", datasourceName)
if err != nil {
return err
}
// create the user preference table
tableSchema := `
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS user_preference(
preference_id TEXT NOT NULL,
preference_value TEXT,
user_id TEXT NOT NULL,
PRIMARY KEY (preference_id,user_id),
FOREIGN KEY (user_id)
REFERENCES users(id)
ON UPDATE CASCADE
ON DELETE CASCADE
);`
_, err = db.Exec(tableSchema)
if err != nil {
return fmt.Errorf("error in creating user_preference table: %s", err.Error())
}
// create the org preference table
tableSchema = `
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS org_preference(
preference_id TEXT NOT NULL,
preference_value TEXT,
org_id TEXT NOT NULL,
PRIMARY KEY (preference_id,org_id),
FOREIGN KEY (org_id)
REFERENCES organizations(id)
ON UPDATE CASCADE
ON DELETE CASCADE
);`
_, err = db.Exec(tableSchema)
if err != nil {
return fmt.Errorf("error in creating org_preference table: %s", err.Error())
}
return nil
}
// org preference functions
func GetOrgPreference(ctx context.Context, preferenceId string, orgId string) (*PreferenceKV, *model.ApiError) {
// check if the preference key exists or not
preference, seen := preferenceMap[preferenceId]
if !seen {
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no such preferenceId exists: %s", preferenceId)}
}
// check if the preference is enabled for org scope or not
isPreferenceEnabled := preference.IsEnabledForScope(OrgAllowedScope)
if !isPreferenceEnabled {
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("preference is not enabled at org scope: %s", preferenceId)}
}
// fetch the value from the database
var orgPreference PreferenceKV
query := `SELECT preference_id , preference_value FROM org_preference WHERE preference_id=$1 AND org_id=$2;`
err := db.Get(&orgPreference, query, preferenceId, orgId)
// if the value doesn't exist in db then return the default value
if err != nil {
if err == sql.ErrNoRows {
return &PreferenceKV{
PreferenceId: preferenceId,
PreferenceValue: preference.DefaultValue,
}, nil
}
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in fetching the org preference: %s", err.Error())}
}
// else return the value fetched from the org_preference table
return &PreferenceKV{
PreferenceId: preferenceId,
PreferenceValue: preference.SanitizeValue(orgPreference.PreferenceValue),
}, nil
}
func UpdateOrgPreference(ctx context.Context, preferenceId string, preferenceValue interface{}, orgId string) (*PreferenceKV, *model.ApiError) {
// check if the preference key exists or not
preference, seen := preferenceMap[preferenceId]
if !seen {
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no such preferenceId exists: %s", preferenceId)}
}
// check if the preference is enabled at org scope or not
isPreferenceEnabled := preference.IsEnabledForScope(OrgAllowedScope)
if !isPreferenceEnabled {
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("preference is not enabled at org scope: %s", preferenceId)}
}
err := preference.IsValidValue(preferenceValue)
if err != nil {
return nil, err
}
// update the values in the org_preference table and return the key and the value
query := `INSERT INTO org_preference(preference_id,preference_value,org_id) VALUES($1,$2,$3)
ON CONFLICT(preference_id,org_id) DO
UPDATE SET preference_value= $2 WHERE preference_id=$1 AND org_id=$3;`
_, dberr := db.Exec(query, preferenceId, preferenceValue, orgId)
if dberr != nil {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in setting the preference value: %s", dberr.Error())}
}
return &PreferenceKV{
PreferenceId: preferenceId,
PreferenceValue: preferenceValue,
}, nil
}
func GetAllOrgPreferences(ctx context.Context, orgId string) (*[]AllPreferences, *model.ApiError) {
// filter out all the org enabled preferences from the preference variable
allOrgPreferences := []AllPreferences{}
// fetch all the org preference values stored in org_preference table
orgPreferenceValues := []PreferenceKV{}
query := `SELECT preference_id,preference_value FROM org_preference WHERE org_id=$1;`
err := db.Select(&orgPreferenceValues, query, orgId)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in getting all org preference values: %s", err)}
}
// create a map of key vs values from the above response
preferenceValueMap := map[string]interface{}{}
for _, preferenceValue := range orgPreferenceValues {
preferenceValueMap[preferenceValue.PreferenceId] = preferenceValue.PreferenceValue
}
// update in the above filtered list wherver value present in the map
for _, preference := range preferenceMap {
isEnabledForOrgScope := preference.IsEnabledForScope(OrgAllowedScope)
if isEnabledForOrgScope {
preferenceWithValue := AllPreferences{}
preferenceWithValue.Key = preference.Key
preferenceWithValue.Name = preference.Name
preferenceWithValue.Description = preference.Description
preferenceWithValue.AllowedScopes = preference.AllowedScopes
preferenceWithValue.AllowedValues = preference.AllowedValues
preferenceWithValue.DefaultValue = preference.DefaultValue
preferenceWithValue.Range = preference.Range
preferenceWithValue.ValueType = preference.ValueType
preferenceWithValue.IsDiscreteValues = preference.IsDiscreteValues
value, seen := preferenceValueMap[preference.Key]
if seen {
preferenceWithValue.Value = value
} else {
preferenceWithValue.Value = preference.DefaultValue
}
preferenceWithValue.Value = preference.SanitizeValue(preferenceWithValue.Value)
allOrgPreferences = append(allOrgPreferences, preferenceWithValue)
}
}
return &allOrgPreferences, nil
}
// user preference functions
func GetUserPreference(ctx context.Context, preferenceId string, orgId string, userId string) (*PreferenceKV, *model.ApiError) {
// check if the preference key exists
preference, seen := preferenceMap[preferenceId]
if !seen {
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no such preferenceId exists: %s", preferenceId)}
}
preferenceValue := PreferenceKV{
PreferenceId: preferenceId,
PreferenceValue: preference.DefaultValue,
}
// check if the preference is enabled at user scope
isPreferenceEnabledAtUserScope := preference.IsEnabledForScope(UserAllowedScope)
if !isPreferenceEnabledAtUserScope {
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("preference is not enabled at user scope: %s", preferenceId)}
}
isPreferenceEnabledAtOrgScope := preference.IsEnabledForScope(OrgAllowedScope)
// get the value from the org scope if enabled at org scope
if isPreferenceEnabledAtOrgScope {
orgPreference := PreferenceKV{}
query := `SELECT preference_id , preference_value FROM org_preference WHERE preference_id=$1 AND org_id=$2;`
err := db.Get(&orgPreference, query, preferenceId, orgId)
// if there is error in getting values and its not an empty rows error return from here
if err != nil && err != sql.ErrNoRows {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in getting org preference values: %s", err.Error())}
}
// if there is no error update the preference value with value from org preference
if err == nil {
preferenceValue.PreferenceValue = orgPreference.PreferenceValue
}
}
// get the value from the user_preference table, if exists return this value else the one calculated in the above step
userPreference := PreferenceKV{}
query := `SELECT preference_id, preference_value FROM user_preference WHERE preference_id=$1 AND user_id=$2;`
err := db.Get(&userPreference, query, preferenceId, userId)
if err != nil && err != sql.ErrNoRows {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in getting user preference values: %s", err.Error())}
}
if err == nil {
preferenceValue.PreferenceValue = userPreference.PreferenceValue
}
return &PreferenceKV{
PreferenceId: preferenceValue.PreferenceId,
PreferenceValue: preference.SanitizeValue(preferenceValue.PreferenceValue),
}, nil
}
func UpdateUserPreference(ctx context.Context, preferenceId string, preferenceValue interface{}, userId string) (*PreferenceKV, *model.ApiError) {
// check if the preference id is valid
preference, seen := preferenceMap[preferenceId]
if !seen {
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no such preferenceId exists: %s", preferenceId)}
}
// check if the preference is enabled at user scope
isPreferenceEnabledAtUserScope := preference.IsEnabledForScope(UserAllowedScope)
if !isPreferenceEnabledAtUserScope {
return nil, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("preference is not enabled at user scope: %s", preferenceId)}
}
err := preference.IsValidValue(preferenceValue)
if err != nil {
return nil, err
}
// update the user preference values
query := `INSERT INTO user_preference(preference_id,preference_value,user_id) VALUES($1,$2,$3)
ON CONFLICT(preference_id,user_id) DO
UPDATE SET preference_value= $2 WHERE preference_id=$1 AND user_id=$3;`
_, dberrr := db.Exec(query, preferenceId, preferenceValue, userId)
if dberrr != nil {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in setting the preference value: %s", dberrr.Error())}
}
return &PreferenceKV{
PreferenceId: preferenceId,
PreferenceValue: preferenceValue,
}, nil
}
func GetAllUserPreferences(ctx context.Context, orgId string, userId string) (*[]AllPreferences, *model.ApiError) {
allUserPreferences := []AllPreferences{}
// fetch all the org preference values stored in org_preference table
orgPreferenceValues := []PreferenceKV{}
query := `SELECT preference_id,preference_value FROM org_preference WHERE org_id=$1;`
err := db.Select(&orgPreferenceValues, query, orgId)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in getting all org preference values: %s", err)}
}
// create a map of key vs values from the above response
preferenceOrgValueMap := map[string]interface{}{}
for _, preferenceValue := range orgPreferenceValues {
preferenceOrgValueMap[preferenceValue.PreferenceId] = preferenceValue.PreferenceValue
}
// fetch all the user preference values stored in user_preference table
userPreferenceValues := []PreferenceKV{}
query = `SELECT preference_id,preference_value FROM user_preference WHERE user_id=$1;`
err = db.Select(&userPreferenceValues, query, userId)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error in getting all user preference values: %s", err)}
}
// create a map of key vs values from the above response
preferenceUserValueMap := map[string]interface{}{}
for _, preferenceValue := range userPreferenceValues {
preferenceUserValueMap[preferenceValue.PreferenceId] = preferenceValue.PreferenceValue
}
// update in the above filtered list wherver value present in the map
for _, preference := range preferenceMap {
isEnabledForUserScope := preference.IsEnabledForScope(UserAllowedScope)
if isEnabledForUserScope {
preferenceWithValue := AllPreferences{}
preferenceWithValue.Key = preference.Key
preferenceWithValue.Name = preference.Name
preferenceWithValue.Description = preference.Description
preferenceWithValue.AllowedScopes = preference.AllowedScopes
preferenceWithValue.AllowedValues = preference.AllowedValues
preferenceWithValue.DefaultValue = preference.DefaultValue
preferenceWithValue.Range = preference.Range
preferenceWithValue.ValueType = preference.ValueType
preferenceWithValue.IsDiscreteValues = preference.IsDiscreteValues
preferenceWithValue.Value = preference.DefaultValue
isEnabledForOrgScope := preference.IsEnabledForScope(OrgAllowedScope)
if isEnabledForOrgScope {
value, seen := preferenceOrgValueMap[preference.Key]
if seen {
preferenceWithValue.Value = value
}
}
value, seen := preferenceUserValueMap[preference.Key]
if seen {
preferenceWithValue.Value = value
}
preferenceWithValue.Value = preference.SanitizeValue(preferenceWithValue.Value)
allUserPreferences = append(allUserPreferences, preferenceWithValue)
}
}
return &allUserPreferences, nil
}

View File

@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
@ -27,6 +28,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
"go.signoz.io/signoz/pkg/query-service/app/opamp"
opAmpModel "go.signoz.io/signoz/pkg/query-service/app/opamp/model"
"go.signoz.io/signoz/pkg/query-service/app/preferences"
"go.signoz.io/signoz/pkg/query-service/common"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
@ -94,6 +96,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
return nil, err
}
if err := preferences.InitDB(constants.RELATIONAL_DATASOURCE_PATH); err != nil {
return nil, err
}
localDB, err := dashboards.InitDB(constants.RELATIONAL_DATASOURCE_PATH)
explorer.InitWithDSN(constants.RELATIONAL_DATASOURCE_PATH)
@ -268,7 +274,21 @@ func (s *Server) createPublicServer(api *APIHandler) (*http.Server, error) {
r.Use(s.analyticsMiddleware)
r.Use(loggingMiddleware)
am := NewAuthMiddleware(auth.GetUserFromRequest)
// add auth middleware
getUserFromRequest := func(r *http.Request) (*model.UserPayload, error) {
user, err := auth.GetUserFromRequest(r)
if err != nil {
return nil, err
}
if user.User.OrgId == "" {
return nil, model.UnauthorizedError(errors.New("orgId is missing in the claims"))
}
return user, nil
}
am := NewAuthMiddleware(getUserFromRequest)
api.RegisterRoutes(r, am)
api.RegisterLogsRoutes(r, am)

View File

@ -467,6 +467,10 @@ func authenticateLogin(ctx context.Context, req *model.LoginRequest) (*model.Use
return nil, errors.Wrap(err, "failed to validate refresh token")
}
if user.OrgId == "" {
return nil, model.UnauthorizedError(errors.New("orgId is missing in the claims"))
}
return user, nil
}
@ -505,6 +509,7 @@ func GenerateJWTForUser(user *model.User) (model.UserJwtObject, error) {
"gid": user.GroupId,
"email": user.Email,
"exp": j.AccessJwtExpiry,
"orgId": user.OrgId,
})
j.AccessJwt, err = token.SignedString([]byte(JwtSecret))
@ -518,6 +523,7 @@ func GenerateJWTForUser(user *model.User) (model.UserJwtObject, error) {
"gid": user.GroupId,
"email": user.Email,
"exp": j.RefreshJwtExpiry,
"orgId": user.OrgId,
})
j.RefreshJwt, err = token.SignedString([]byte(JwtSecret))

View File

@ -20,6 +20,8 @@ var (
)
func ParseJWT(jwtStr string) (jwt.MapClaims, error) {
// TODO[@vikrantgupta25] : to update this to the claims check function for better integrity of JWT
// reference - https://pkg.go.dev/github.com/golang-jwt/jwt/v5#Parser.ParseWithClaims
token, err := jwt.Parse(jwtStr, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.Errorf("unknown signing algo: %v", token.Header["alg"])
@ -35,6 +37,7 @@ func ParseJWT(jwtStr string) (jwt.MapClaims, error) {
if !ok || !token.Valid {
return nil, errors.Errorf("Not a valid jwt claim")
}
return claims, nil
}
@ -47,11 +50,18 @@ func validateUser(tok string) (*model.UserPayload, error) {
if !claims.VerifyExpiresAt(now, true) {
return nil, model.ErrorTokenExpired
}
var orgId string
if claims["orgId"] != nil {
orgId = claims["orgId"].(string)
}
return &model.UserPayload{
User: model.User{
Id: claims["id"].(string),
GroupId: claims["gid"].(string),
Email: claims["email"].(string),
OrgId: orgId,
},
}, nil
}