feat(auth): Add auth and access-controls support (#874)

* auth and rbac support enabled
This commit is contained in:
Ahsan Barkati 2022-05-03 15:26:32 +05:30 committed by GitHub
parent 3ef9d96678
commit 6ccdc5296e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 2313 additions and 328 deletions

2
.gitignore vendored
View File

@ -43,7 +43,7 @@ frontend/cypress.env.json
frontend/*.env
pkg/query-service/signoz.db
pkg/query-service/tframe/test-deploy/data/
pkg/query-service/tests/test-deploy/data/
# local data

View File

@ -103,9 +103,9 @@ func (c *Data) Scan(src interface{}) error {
}
// CreateDashboard creates a new dashboard
func CreateDashboard(data *map[string]interface{}) (*Dashboard, *model.ApiError) {
func CreateDashboard(data map[string]interface{}) (*Dashboard, *model.ApiError) {
dash := &Dashboard{
Data: *data,
Data: data,
}
dash.CreatedAt = time.Now()
dash.UpdatedAt = time.Now()
@ -135,7 +135,7 @@ func CreateDashboard(data *map[string]interface{}) (*Dashboard, *model.ApiError)
return dash, nil
}
func GetDashboards() (*[]Dashboard, *model.ApiError) {
func GetDashboards() ([]Dashboard, *model.ApiError) {
dashboards := []Dashboard{}
query := fmt.Sprintf("SELECT * FROM dashboards;")
@ -145,7 +145,7 @@ func GetDashboards() (*[]Dashboard, *model.ApiError) {
return nil, &model.ApiError{Typ: model.ErrorExec, Err: err}
}
return &dashboards, nil
return dashboards, nil
}
func DeleteDashboard(uuid string) *model.ApiError {
@ -182,7 +182,7 @@ func GetDashboard(uuid string) (*Dashboard, *model.ApiError) {
return &dashboard, nil
}
func UpdateDashboard(uuid string, data *map[string]interface{}) (*Dashboard, *model.ApiError) {
func UpdateDashboard(uuid string, data map[string]interface{}) (*Dashboard, *model.ApiError) {
map_data, err := json.Marshal(data)
if err != nil {
@ -196,7 +196,7 @@ func UpdateDashboard(uuid string, data *map[string]interface{}) (*Dashboard, *mo
}
dashboard.UpdatedAt = time.Now()
dashboard.Data = *data
dashboard.Data = data
// db.Prepare("Insert into dashboards where")
_, err = db.Exec("UPDATE dashboards SET updated_at=$1, data=$2 WHERE uuid=$3 ", dashboard.UpdatedAt, map_data, dashboard.Uuid)

View File

@ -42,7 +42,7 @@ func readCurrentDir(dir string) error {
continue
}
_, apiErr = CreateDashboard(&data)
_, apiErr = CreateDashboard(data)
if apiErr != nil {
zap.S().Errorf("Creating Dashboards: Error in file: %s\t%s", filename, apiErr.Err)
continue

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,10 @@ import (
"strconv"
"time"
"github.com/gorilla/mux"
promModel "github.com/prometheus/common/model"
"go.signoz.io/query-service/auth"
"go.signoz.io/query-service/constants"
"go.signoz.io/query-service/model"
)
@ -19,8 +22,7 @@ var allowedFunctions = []string{"count", "ratePerSec", "sum", "avg", "min", "max
func parseUser(r *http.Request) (*model.User, error) {
var user model.User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
return nil, err
}
if len(user.Email) == 0 {
@ -585,14 +587,84 @@ func parseGetTTL(r *http.Request) (*model.GetTTLParams, error) {
return &model.GetTTLParams{Type: typeTTL, GetAllTTL: getAllTTL}, nil
}
func parseUserPreferences(r *http.Request) (*model.UserPreferences, error) {
func parseUserRequest(r *http.Request) (*model.User, error) {
var req model.User
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return &req, nil
}
var userPreferences model.UserPreferences
err := json.NewDecoder(r.Body).Decode(&userPreferences)
if err != nil {
func parseInviteRequest(r *http.Request) (*model.InviteRequest, error) {
var req model.InviteRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return &req, nil
}
func parseRegisterRequest(r *http.Request) (*auth.RegisterRequest, error) {
var req auth.RegisterRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return &userPreferences, nil
if err := auth.ValidatePassword(req.Password); err != nil {
return nil, err
}
return &req, nil
}
func parseLoginRequest(r *http.Request) (*model.LoginRequest, error) {
var req model.LoginRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return &req, nil
}
func parseUserRoleRequest(r *http.Request) (*model.UserRole, error) {
var req model.UserRole
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return &req, nil
}
func parseEditOrgRequest(r *http.Request) (*model.Organization, error) {
var req model.Organization
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return &req, nil
}
func parseResetPasswordRequest(r *http.Request) (*model.ResetPasswordRequest, error) {
var req model.ResetPasswordRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
if err := auth.ValidatePassword(req.Password); err != nil {
return nil, err
}
return &req, nil
}
func parseChangePasswordRequest(r *http.Request) (*model.ChangePasswordRequest, error) {
id := mux.Vars(r)["id"]
var req model.ChangePasswordRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
req.UserId = id
if err := auth.ValidatePassword(req.NewPassword); err != nil {
return nil, err
}
return &req, nil
}

View File

@ -69,6 +69,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
// return nil, err
// }
if err := dao.InitDao("sqlite", constants.RELATIONAL_DATASOURCE_PATH); err != nil {
return nil, err
}
s := &Server{
// logger: logger,
// querySvc: querySvc,
@ -113,12 +117,7 @@ func (s *Server) createHTTPServer() (*http.Server, error) {
return nil, fmt.Errorf("Storage type: %s is not supported in query service", storage)
}
relationalDB, err := dao.FactoryDao("sqlite")
if err != nil {
return nil, err
}
apiHandler, err := NewAPIHandler(&reader, relationalDB)
apiHandler, err := NewAPIHandler(&reader, dao.DB())
if err != nil {
return nil, err
}
@ -136,6 +135,7 @@ func (s *Server) createHTTPServer() (*http.Server, error) {
AllowedOrigins: []string{"*"},
// AllowCredentials: true,
AllowedMethods: []string{"GET", "DELETE", "POST", "PUT"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
})
handler := c.Handler(r)

View File

@ -0,0 +1,373 @@
package auth
import (
"context"
"fmt"
"time"
"github.com/golang-jwt/jwt"
"github.com/google/uuid"
"github.com/pkg/errors"
"go.signoz.io/query-service/constants"
"go.signoz.io/query-service/dao"
"go.signoz.io/query-service/model"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
)
const (
opaqueTokenSize = 16
minimumPasswordLength = 8
)
var (
ErrorInvalidCreds = fmt.Errorf("Invalid credentials")
)
// The root user should be able to invite people to create account on SigNoz cluster.
func Invite(ctx context.Context, req *model.InviteRequest) (*model.InviteResponse, error) {
zap.S().Debugf("Got an invite request for email: %s\n", req.Email)
token, err := randomHex(opaqueTokenSize)
if err != nil {
return nil, errors.Wrap(err, "failed to generate invite token")
}
user, apiErr := dao.DB().GetUserByEmail(ctx, req.Email)
if apiErr != nil {
return nil, errors.Wrap(apiErr.Err, "Failed to check already existing user")
}
if user != nil {
return nil, errors.New("User already exists with the same email")
}
if err := validateInviteRequest(req); err != nil {
return nil, errors.Wrap(err, "invalid invite request")
}
jwtAdmin, err := ExtractJwtFromContext(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to extract admin jwt token")
}
adminUser, err := validateUser(jwtAdmin)
if err != nil {
return nil, errors.Wrap(err, "failed to validate admin jwt token")
}
au, apiErr := dao.DB().GetUser(ctx, adminUser.Id)
if apiErr != nil {
return nil, errors.Wrap(err, "failed to query admin user from the DB")
}
inv := &model.InvitationObject{
Name: req.Name,
Email: req.Email,
Token: token,
CreatedAt: time.Now().Unix(),
Role: req.Role,
OrgId: au.OrgId,
}
if err := dao.DB().CreateInviteEntry(ctx, inv); err != nil {
return nil, errors.Wrap(err.Err, "failed to write to DB")
}
return &model.InviteResponse{Email: inv.Email, InviteToken: inv.Token}, nil
}
// RevokeInvite is used to revoke the invitation for the given email.
func RevokeInvite(ctx context.Context, email string) error {
zap.S().Debugf("RevokeInvite method invoked for email: %s\n", email)
if !isValidEmail(email) {
return ErrorInvalidInviteToken
}
if err := dao.DB().DeleteInvitation(ctx, email); err != nil {
return errors.Wrap(err.Err, "failed to write to DB")
}
return nil
}
// GetInvite returns an invitation object for the given token.
func GetInvite(ctx context.Context, token string) (*model.InvitationResponseObject, error) {
zap.S().Debugf("GetInvite method invoked for token: %s\n", token)
inv, apiErr := dao.DB().GetInviteFromToken(ctx, token)
if apiErr != nil {
return nil, errors.Wrap(apiErr.Err, "failed to query the DB")
}
if inv == nil {
return nil, errors.New("user is not invited")
}
// TODO(Ahsan): This is not the best way to add org name in the invite response. We should
// either include org name in the invite table or do a join query.
org, apiErr := dao.DB().GetOrg(ctx, inv.OrgId)
if apiErr != nil {
return nil, errors.Wrap(apiErr.Err, "failed to query the DB")
}
return &model.InvitationResponseObject{
Name: inv.Name,
Email: inv.Email,
Token: inv.Token,
CreatedAt: inv.CreatedAt,
Role: inv.Role,
Organization: org.Name,
}, nil
}
func validateInvite(ctx context.Context, req *RegisterRequest) (*model.InvitationObject, error) {
invitation, err := dao.DB().GetInviteFromEmail(ctx, req.Email)
if err != nil {
return nil, errors.Wrap(err.Err, "Failed to read from DB")
}
if invitation == nil || invitation.Token != req.InviteToken {
return nil, ErrorInvalidInviteToken
}
return invitation, nil
}
func CreateResetPasswordToken(ctx context.Context, userId string) (*model.ResetPasswordEntry, error) {
token, err := randomHex(opaqueTokenSize)
if err != nil {
return nil, errors.Wrap(err, "failed to generate reset password token")
}
req := &model.ResetPasswordEntry{
UserId: userId,
Token: token,
}
if apiErr := dao.DB().CreateResetPasswordEntry(ctx, req); err != nil {
return nil, errors.Wrap(apiErr.Err, "failed to write to DB")
}
return req, nil
}
func ResetPassword(ctx context.Context, req *model.ResetPasswordRequest) error {
entry, apiErr := dao.DB().GetResetPasswordEntry(ctx, req.Token)
if apiErr != nil {
return errors.Wrap(apiErr.Err, "failed to query the DB")
}
if entry == nil {
return errors.New("Invalid reset password request")
}
hash, err := passwordHash(req.Password)
if err != nil {
return errors.Wrap(err, "Failed to generate password hash")
}
if apiErr := dao.DB().UpdateUserPassword(ctx, hash, entry.UserId); apiErr != nil {
return apiErr.Err
}
if apiErr := dao.DB().DeleteResetPasswordEntry(ctx, req.Token); apiErr != nil {
return errors.Wrap(apiErr.Err, "failed to delete reset token from DB")
}
return nil
}
func ChangePassword(ctx context.Context, req *model.ChangePasswordRequest) error {
user, apiErr := dao.DB().GetUser(ctx, req.UserId)
if apiErr != nil {
return errors.Wrap(apiErr.Err, "failed to query user from the DB")
}
if user == nil || !passwordMatch(user.Password, req.OldPassword) {
return ErrorInvalidCreds
}
hash, err := passwordHash(req.NewPassword)
if err != nil {
return errors.Wrap(err, "Failed to generate password hash")
}
if apiErr := dao.DB().UpdateUserPassword(ctx, hash, user.Id); apiErr != nil {
return apiErr.Err
}
return nil
}
type RegisterRequest struct {
Name string `json:"name"`
OrgName string `json:"orgName"`
Email string `json:"email"`
Password string `json:"password"`
InviteToken string `json:"token"`
}
// Register registers a new user. For the first register request, it doesn't need an invite token
// and also the first registration is an enforced ADMIN registration. Every subsequent request will
// need an invite token to go through.
func Register(ctx context.Context, req *RegisterRequest) *model.ApiError {
zap.S().Debugf("Got a register request for email: %v\n", req.Email)
// TODO(Ahsan): We should optimize it, shouldn't make an extra DB call everytime to know if
// this is the first register request.
users, apiErr := dao.DB().GetUsers(ctx)
if apiErr != nil {
zap.S().Debugf("GetUser failed, err: %v\n", apiErr.Err)
return apiErr
}
var groupName, orgId string
// If there are no user, then this first user is granted Admin role. Also, an org is created
// based on the request. Any other user can't use any other org name, if they do then
// registration will fail because of foreign key violation while create user.
// TODO(Ahsan): We need to re-work this logic for the case of multi-tenant system.
if len(users) == 0 {
org, apiErr := dao.DB().CreateOrg(ctx, &model.Organization{Name: req.OrgName})
if apiErr != nil {
zap.S().Debugf("CreateOrg failed, err: %v\n", apiErr.Err)
return apiErr
}
groupName = constants.AdminGroup
orgId = org.Id
}
if len(users) > 0 {
inv, err := validateInvite(ctx, req)
if err != nil {
return &model.ApiError{Err: err, Typ: model.ErrorUnauthorized}
}
org, apiErr := dao.DB().GetOrgByName(ctx, req.OrgName)
if apiErr != nil {
zap.S().Debugf("GetOrgByName failed, err: %v\n", apiErr.Err)
return apiErr
}
groupName = inv.Role
if org != nil {
orgId = org.Id
}
}
group, apiErr := dao.DB().GetGroupByName(ctx, groupName)
if apiErr != nil {
zap.S().Debugf("GetGroupByName failed, err: %v\n", apiErr.Err)
return apiErr
}
hash, err := passwordHash(req.Password)
if err != nil {
return &model.ApiError{Err: err, Typ: model.ErrorUnauthorized}
}
user := &model.User{
Id: uuid.NewString(),
Name: req.Name,
Email: req.Email,
Password: hash,
CreatedAt: time.Now().Unix(),
ProfilePirctureURL: "", // Currently unused
GroupId: group.Id,
OrgId: orgId,
}
// TODO(Ahsan): Ideally create user and delete invitation should happen in a txn.
_, apiErr = dao.DB().CreateUser(ctx, user)
if apiErr != nil {
zap.S().Debugf("CreateUser failed, err: %v\n", apiErr.Err)
return apiErr
}
return dao.DB().DeleteInvitation(ctx, user.Email)
}
// Login method returns access and refresh tokens on successful login, else it errors out.
func Login(ctx context.Context, request *model.LoginRequest) (*model.LoginResponse, error) {
zap.S().Debugf("Login method called for user: %s\n", request.Email)
user, err := authenticateLogin(ctx, request)
if err != nil {
zap.S().Debugf("Failed to authenticate login request, %v", err)
return nil, err
}
accessJwtExpiry := time.Now().Add(JwtExpiry).Unix()
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"id": user.Id,
"gid": user.GroupId,
"email": user.Email,
"exp": accessJwtExpiry,
})
accessJwt, err := token.SignedString([]byte(JwtSecret))
if err != nil {
return nil, errors.Errorf("failed to encode jwt: %v", err)
}
refreshJwtExpiry := time.Now().Add(JwtRefresh).Unix()
token = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"id": user.Id,
"gid": user.GroupId,
"email": user.Email,
"exp": refreshJwtExpiry,
})
refreshJwt, err := token.SignedString([]byte(JwtSecret))
if err != nil {
return nil, errors.Errorf("failed to encode jwt: %v", err)
}
return &model.LoginResponse{
AccessJwt: accessJwt,
AccessJwtExpiry: accessJwtExpiry,
RefreshJwt: refreshJwt,
RefreshJwtExpiry: refreshJwtExpiry,
UserId: user.Id,
}, nil
}
// authenticateLogin is responsible for querying the DB and validating the credentials.
func authenticateLogin(ctx context.Context, req *model.LoginRequest) (*model.UserPayload, error) {
// If refresh token is valid, then simply authorize the login request.
if len(req.RefreshToken) > 0 {
user, err := validateUser(req.RefreshToken)
if err != nil {
return nil, errors.Wrap(err, "failed to validate refresh token")
}
return user, nil
}
user, err := dao.DB().GetUserByEmail(ctx, req.Email)
if err != nil {
return nil, errors.Wrap(err.Err, "user not found")
}
if user == nil || !passwordMatch(user.Password, req.Password) {
return nil, ErrorInvalidCreds
}
return user, nil
}
// Generate hash from the password.
func passwordHash(pass string) (string, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hash), nil
}
// Checks if the given password results in the given hash.
func passwordMatch(hash, password string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
if err != nil {
return false
}
return true
}

View File

@ -0,0 +1,94 @@
package auth
import (
"context"
"net/http"
"time"
jwtmiddleware "github.com/auth0/go-jwt-middleware"
"github.com/golang-jwt/jwt"
"github.com/pkg/errors"
"go.signoz.io/query-service/model"
"go.uber.org/zap"
"google.golang.org/grpc/metadata"
)
var (
JwtSecret string
JwtExpiry = 30 * time.Minute
JwtRefresh = 24 * time.Hour
)
func ParseJWT(jwtStr string) (jwt.MapClaims, error) {
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"])
}
return []byte(JwtSecret), nil
})
if err != nil {
return nil, errors.Wrapf(err, "failed to parse jwt token")
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok || !token.Valid {
return nil, errors.Errorf("Not a valid jwt claim")
}
return claims, nil
}
func validateUser(tok string) (*model.UserPayload, error) {
claims, err := ParseJWT(tok)
if err != nil {
return nil, err
}
now := time.Now().Unix()
if !claims.VerifyExpiresAt(now, true) {
return nil, errors.Errorf("Token is expired")
}
return &model.UserPayload{
User: model.User{
Id: claims["id"].(string),
GroupId: claims["gid"].(string),
Email: claims["email"].(string),
},
}, nil
}
// AttachJwtToContext attached the jwt token from the request header to the context.
func AttachJwtToContext(ctx context.Context, r *http.Request) context.Context {
token, err := ExtractJwtFromRequest(r)
if err != nil {
zap.S().Debugf("Error while getting token from header, %v", err)
return ctx
}
if len(token) > 0 {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
md = metadata.New(nil)
}
md.Append("accessJwt", token)
ctx = metadata.NewIncomingContext(ctx, md)
}
return ctx
}
func ExtractJwtFromContext(ctx context.Context) (string, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return "", errors.New("No JWT metadata token found")
}
accessJwt := md.Get("accessJwt")
if len(accessJwt) == 0 {
return "", errors.New("No JWT token found")
}
return accessJwt[0], nil
}
func ExtractJwtFromRequest(r *http.Request) (string, error) {
return jwtmiddleware.FromAuthHeader(r)
}

View File

@ -0,0 +1,136 @@
package auth
import (
"context"
"fmt"
"net/http"
"regexp"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"go.signoz.io/query-service/constants"
"go.signoz.io/query-service/dao"
"go.uber.org/zap"
)
type Group struct {
GroupID string
GroupName string
}
type AuthCache struct {
AdminGroupId string
EditorGroupId string
ViewerGroupId string
}
var AuthCacheObj AuthCache
// InitAuthCache reads the DB and initialize the auth cache.
func InitAuthCache(ctx context.Context) error {
setGroupId := func(groupName string, dest *string) error {
group, err := dao.DB().GetGroupByName(ctx, groupName)
if err != nil {
return errors.Wrapf(err.Err, "failed to get group %s", groupName)
}
*dest = group.Id
return nil
}
if err := setGroupId(constants.AdminGroup, &AuthCacheObj.AdminGroupId); err != nil {
return err
}
if err := setGroupId(constants.EditorGroup, &AuthCacheObj.EditorGroupId); err != nil {
return err
}
if err := setGroupId(constants.ViewerGroup, &AuthCacheObj.ViewerGroupId); err != nil {
return err
}
return nil
}
func IsAdmin(r *http.Request) bool {
accessJwt, err := ExtractJwtFromRequest(r)
if err != nil {
zap.S().Debugf("Failed to verify admin access, err: %v", err)
return false
}
user, err := validateUser(accessJwt)
if err != nil {
return false
}
return user.GroupId == AuthCacheObj.AdminGroupId
}
func IsSelfAccessRequest(r *http.Request) bool {
id := mux.Vars(r)["id"]
accessJwt, err := ExtractJwtFromRequest(r)
if err != nil {
zap.S().Debugf("Failed to verify self access, err: %v", err)
return false
}
user, err := validateUser(accessJwt)
if err != nil {
zap.S().Debugf("Failed to verify self access, err: %v", err)
return false
}
zap.S().Debugf("Self access verification, userID: %s, id: %s\n", user.Id, id)
return user.Id == id
}
func IsViewer(r *http.Request) bool {
accessJwt, err := ExtractJwtFromRequest(r)
if err != nil {
zap.S().Debugf("Failed to verify viewer access, err: %v", err)
return false
}
user, err := validateUser(accessJwt)
if err != nil {
return false
}
return user.GroupId == AuthCacheObj.ViewerGroupId
}
func IsEditor(r *http.Request) bool {
accessJwt, err := ExtractJwtFromRequest(r)
if err != nil {
zap.S().Debugf("Failed to verify editor access, err: %v", err)
return false
}
user, err := validateUser(accessJwt)
if err != nil {
return false
}
return user.GroupId == AuthCacheObj.EditorGroupId
}
func ValidatePassword(password string) error {
if len(password) < minimumPasswordLength {
return errors.Errorf("Password should be atleast %d characters.", minimumPasswordLength)
}
num := `[0-9]{1}`
lower := `[a-z]{1}`
upper := `[A-Z]{1}`
symbol := `[!@#$&*]{1}`
if b, err := regexp.MatchString(num, password); !b || err != nil {
return fmt.Errorf("password should have atleast one number")
}
if b, err := regexp.MatchString(lower, password); !b || err != nil {
return fmt.Errorf("password should have atleast one lower case letter")
}
if b, err := regexp.MatchString(upper, password); !b || err != nil {
return fmt.Errorf("password should have atleast one upper case letter")
}
if b, err := regexp.MatchString(symbol, password); !b || err != nil {
return fmt.Errorf("password should have atleast one special character from !@#$&* ")
}
return nil
}

View File

@ -0,0 +1,55 @@
package auth
import (
"crypto/rand"
"encoding/hex"
"github.com/pkg/errors"
"go.signoz.io/query-service/constants"
"go.signoz.io/query-service/model"
)
var (
ErrorEmptyRequest = errors.New("Empty request")
ErrorInvalidEmail = errors.New("Invalid email")
ErrorInvalidRole = errors.New("Invalid role")
ErrorInvalidInviteToken = errors.New("Invalid invite token")
)
func randomHex(sz int) (string, error) {
bytes := make([]byte, sz)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
func isValidRole(role string) bool {
switch role {
case constants.AdminGroup, constants.EditorGroup, constants.ViewerGroup:
return true
default:
return false
}
return false
}
func validateInviteRequest(req *model.InviteRequest) error {
if req == nil {
return ErrorEmptyRequest
}
if !isValidEmail(req.Email) {
return ErrorInvalidEmail
}
if !isValidRole(req.Role) {
return ErrorInvalidRole
}
return nil
}
// TODO(Ahsan): Implement check on email semantic.
func isValidEmail(email string) bool {
return true
}

View File

@ -0,0 +1,7 @@
package constants
const (
AdminGroup = "ADMIN"
EditorGroup = "EDITOR"
ViewerGroup = "VIEWER"
)

View File

@ -5,7 +5,9 @@ import (
"strconv"
)
const HTTPHostPort = "0.0.0.0:8080"
const (
HTTPHostPort = "0.0.0.0:8080"
)
var DEFAULT_TELEMETRY_ANONYMOUS = false
@ -28,7 +30,7 @@ func GetAlertManagerApiPrefix() string {
return "http://alertmanager:9093/api/"
}
// Alert manager channel subpath
// Alert manager channel subpath
var AmChannelApiPath = GetOrDefaultEnv("ALERTMANAGER_API_CHANNEL_PATH", "v1/routes")
var RELATIONAL_DATASOURCE_PATH = GetOrDefaultEnv("SIGNOZ_LOCAL_DB_PATH", "/var/lib/signoz/signoz.db")
@ -56,11 +58,10 @@ const (
ContextTimeout = 60 // seconds
)
func GetOrDefaultEnv(key string, fallback string) string {
v := os.Getenv(key)
if len(v) == 0 {
return fallback
}
return v
}
}

View File

@ -3,24 +3,31 @@ package dao
import (
"fmt"
"go.signoz.io/query-service/constants"
"go.signoz.io/query-service/dao/interfaces"
"github.com/pkg/errors"
"go.signoz.io/query-service/dao/sqlite"
)
func FactoryDao(engine string) (*interfaces.ModelDao, error) {
var i interfaces.ModelDao
var db ModelDao
func InitDao(engine, path string) error {
var err error
switch engine {
case "sqlite":
i, err = sqlite.InitDB(constants.RELATIONAL_DATASOURCE_PATH)
db, err = sqlite.InitDB(path)
if err != nil {
return nil, err
return errors.Wrap(err, "failed to initialize DB")
}
default:
return nil, fmt.Errorf("RelationalDB type: %s is not supported in query service", engine)
return fmt.Errorf("RelationalDB type: %s is not supported in query service", engine)
}
return &i, nil
return nil
}
func DB() ModelDao {
if db == nil {
// Should never reach here
panic("GetDB called before initialization")
}
return db
}

View File

@ -0,0 +1,56 @@
package dao
import (
"context"
"go.signoz.io/query-service/model"
)
type ModelDao interface {
Queries
Mutations
}
type Queries interface {
GetInviteFromEmail(ctx context.Context, email string) (*model.InvitationObject, *model.ApiError)
GetInviteFromToken(ctx context.Context, token string) (*model.InvitationObject, *model.ApiError)
GetInvites(ctx context.Context) ([]model.InvitationObject, *model.ApiError)
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)
GetGroup(ctx context.Context, id string) (*model.Group, *model.ApiError)
GetGroupByName(ctx context.Context, name string) (*model.Group, *model.ApiError)
GetGroups(ctx context.Context) ([]model.Group, *model.ApiError)
GetOrgs(ctx context.Context) ([]model.Organization, *model.ApiError)
GetOrgByName(ctx context.Context, name string) (*model.Organization, *model.ApiError)
GetOrg(ctx context.Context, id string) (*model.Organization, *model.ApiError)
GetResetPasswordEntry(ctx context.Context, token string) (*model.ResetPasswordEntry, *model.ApiError)
GetUsersByOrg(ctx context.Context, orgId string) ([]model.UserPayload, *model.ApiError)
GetUsersByGroup(ctx context.Context, groupId string) ([]model.UserPayload, *model.ApiError)
}
type Mutations interface {
CreateInviteEntry(ctx context.Context, req *model.InvitationObject) *model.ApiError
DeleteInvitation(ctx context.Context, email string) *model.ApiError
CreateUser(ctx context.Context, user *model.User) (*model.User, *model.ApiError)
EditUser(ctx context.Context, update *model.User) (*model.User, *model.ApiError)
DeleteUser(ctx context.Context, id string) *model.ApiError
CreateGroup(ctx context.Context, group *model.Group) (*model.Group, *model.ApiError)
DeleteGroup(ctx context.Context, id string) *model.ApiError
CreateOrg(ctx context.Context, org *model.Organization) (*model.Organization, *model.ApiError)
EditOrg(ctx context.Context, org *model.Organization) *model.ApiError
DeleteOrg(ctx context.Context, id string) *model.ApiError
CreateResetPasswordEntry(ctx context.Context, req *model.ResetPasswordEntry) *model.ApiError
DeleteResetPasswordEntry(ctx context.Context, token string) *model.ApiError
UpdateUserPassword(ctx context.Context, hash, userId string) *model.ApiError
UpdateUserGroup(ctx context.Context, userId, groupId string) *model.ApiError
}

View File

@ -1,5 +0,0 @@
package interfaces
type ModelDao interface {
UserPreferenceDao
}

View File

@ -1,12 +0,0 @@
package interfaces
import (
"context"
"go.signoz.io/query-service/model"
)
type UserPreferenceDao interface {
UpdateUserPreferece(ctx context.Context, userPreferences *model.UserPreferences) *model.ApiError
FetchUserPreference(ctx context.Context) (*model.UserPreferences, *model.ApiError)
}

View File

@ -5,8 +5,11 @@ import (
"fmt"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
"go.signoz.io/query-service/constants"
"go.signoz.io/query-service/model"
"go.signoz.io/query-service/telemetry"
"go.uber.org/zap"
)
type ModelDaoSqlite struct {
@ -19,52 +22,138 @@ func InitDB(dataSourceName string) (*ModelDaoSqlite, error) {
db, err := sqlx.Open("sqlite3", dataSourceName)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to Open sqlite3 DB")
}
db.SetMaxOpenConns(10)
table_schema := `CREATE TABLE IF NOT EXISTS user_preferences (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT NOT NULL,
isAnonymous INTEGER NOT NULL DEFAULT 0 CHECK(isAnonymous IN (0,1)),
hasOptedUpdates INTEGER NOT NULL DEFAULT 1 CHECK(hasOptedUpdates IN (0,1))
);`
table_schema := `
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS invites (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
token TEXT NOT NULL,
created_at INTEGER NOT NULL,
role TEXT NOT NULL,
org_id TEXT NOT NULL,
FOREIGN KEY(org_id) REFERENCES organizations(id)
);
CREATE TABLE IF NOT EXISTS organizations (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
created_at INTEGER NOT NULL,
is_anonymous INTEGER NOT NULL DEFAULT 0 CHECK(is_anonymous IN (0,1)),
has_opted_updates INTEGER NOT NULL DEFAULT 1 CHECK(has_opted_updates IN (0,1))
);
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
created_at INTEGER NOT NULL,
profile_picture_url TEXT,
group_id TEXT NOT NULL,
org_id TEXT NOT NULL,
FOREIGN KEY(group_id) REFERENCES groups(id),
FOREIGN KEY(org_id) REFERENCES organizations(id)
);
CREATE TABLE IF NOT EXISTS groups (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS reset_password_request (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT NOT NULL,
token TEXT NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id)
);
`
_, err = db.Exec(table_schema)
if err != nil {
return nil, fmt.Errorf("Error in creating user_preferences table: %s", err.Error())
return nil, fmt.Errorf("Error in creating tables: %v", err.Error())
}
mds := &ModelDaoSqlite{db: db}
err = mds.initializeUserPreferences()
if err != nil {
ctx := context.Background()
if err := mds.initializeOrgPreferences(ctx); err != nil {
return nil, err
}
if err := mds.initializeRBAC(ctx); err != nil {
return nil, err
}
return mds, nil
return mds, nil
}
func (mds *ModelDaoSqlite) initializeUserPreferences() error {
// initializeOrgPreferences initializes in-memory telemetry settings. It is planned to have
// multiple orgs in the system. In case of multiple orgs, there will be separate instance
// of in-memory telemetry for each of the org, having their own settings. As of now, we only
// have one org so this method relies on the settings of this org to initialize the telemetry etc.
// TODO(Ahsan): Make it multi-tenant when we move to a system with multiple orgs.
func (mds *ModelDaoSqlite) initializeOrgPreferences(ctx context.Context) error {
// set anonymous setting as default in case of any failures to fetch UserPreference in below section
telemetry.GetInstance().SetTelemetryAnonymous(constants.DEFAULT_TELEMETRY_ANONYMOUS)
ctx := context.Background()
userPreference, apiError := mds.FetchUserPreference(ctx)
orgs, apiError := mds.GetOrgs(ctx)
if apiError != nil {
return apiError.Err
}
if apiError != nil {
return apiError.Err
if len(orgs) > 1 {
return errors.Errorf("Found %d organizations, expected one or none.", len(orgs))
}
if userPreference == nil {
userPreference, apiError = mds.CreateDefaultUserPreference(ctx)
}
if apiError != nil {
return apiError.Err
var org model.Organization
if len(orgs) == 1 {
org = orgs[0]
}
// set telemetry fields from userPreferences
telemetry.GetInstance().SetTelemetryAnonymous(userPreference.GetIsAnonymous())
telemetry.GetInstance().SetDistinctId(userPreference.GetUUID())
telemetry.GetInstance().SetTelemetryAnonymous(org.IsAnonymous)
telemetry.GetInstance().SetDistinctId(org.Id)
return nil
}
// initializeRBAC creates the ADMIN, EDITOR and VIEWER groups if they are not present.
func (mds *ModelDaoSqlite) initializeRBAC(ctx context.Context) error {
f := func(groupName string) error {
_, err := mds.createGroupIfNotPresent(ctx, groupName)
return errors.Wrap(err, "Failed to create group")
}
if err := f(constants.AdminGroup); err != nil {
return err
}
if err := f(constants.EditorGroup); err != nil {
return err
}
if err := f(constants.ViewerGroup); err != nil {
return err
}
return nil
}
func (mds *ModelDaoSqlite) createGroupIfNotPresent(ctx context.Context,
name string) (*model.Group, error) {
group, err := mds.GetGroupByName(ctx, name)
if err != nil {
return nil, errors.Wrap(err.Err, "Failed to query for root group")
}
if group != nil {
return group, nil
}
zap.S().Debugf("%s is not found, creating it", name)
group, cErr := mds.CreateGroup(ctx, &model.Group{Name: name})
if cErr != nil {
return nil, cErr.Err
}
return group, nil
}

View File

@ -0,0 +1,512 @@
package sqlite
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
"go.signoz.io/query-service/model"
"go.signoz.io/query-service/telemetry"
)
func (mds *ModelDaoSqlite) CreateInviteEntry(ctx context.Context,
req *model.InvitationObject) *model.ApiError {
_, err := mds.db.ExecContext(ctx,
`INSERT INTO invites (email, name, token, role, created_at, org_id)
VALUES (?, ?, ?, ?, ?, ?);`,
req.Email, req.Name, req.Token, req.Role, req.CreatedAt, req.OrgId)
if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return nil
}
func (mds *ModelDaoSqlite) DeleteInvitation(ctx context.Context, email string) *model.ApiError {
_, err := mds.db.ExecContext(ctx, `DELETE from invites where email=?;`, email)
if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return nil
}
func (mds *ModelDaoSqlite) GetInviteFromEmail(ctx context.Context, email string,
) (*model.InvitationObject, *model.ApiError) {
invites := []model.InvitationObject{}
err := mds.db.Select(&invites,
`SELECT * FROM invites WHERE email=?;`, email)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
if len(invites) > 1 {
return nil, &model.ApiError{
Typ: model.ErrorInternal,
Err: errors.Errorf("Found multiple invites for the email: %s", email)}
}
if len(invites) == 0 {
return nil, nil
}
return &invites[0], nil
}
func (mds *ModelDaoSqlite) GetInviteFromToken(ctx context.Context, token string,
) (*model.InvitationObject, *model.ApiError) {
invites := []model.InvitationObject{}
err := mds.db.Select(&invites,
`SELECT * FROM invites WHERE token=?;`, token)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
if len(invites) > 1 {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
if len(invites) == 0 {
return nil, nil
}
return &invites[0], nil
}
func (mds *ModelDaoSqlite) GetInvites(ctx context.Context,
) ([]model.InvitationObject, *model.ApiError) {
invites := []model.InvitationObject{}
err := mds.db.Select(&invites, "SELECT * FROM invites")
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return invites, nil
}
func (mds *ModelDaoSqlite) CreateOrg(ctx context.Context,
org *model.Organization) (*model.Organization, *model.ApiError) {
org.Id = uuid.NewString()
org.CreatedAt = time.Now().Unix()
_, err := mds.db.ExecContext(ctx,
`INSERT INTO organizations (id, name, created_at) VALUES (?, ?, ?);`,
org.Id, org.Name, org.CreatedAt)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return org, nil
}
func (mds *ModelDaoSqlite) GetOrg(ctx context.Context,
id string) (*model.Organization, *model.ApiError) {
orgs := []model.Organization{}
err := mds.db.Select(&orgs, `SELECT * FROM organizations WHERE id=?;`, id)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
if len(orgs) > 1 {
return nil, &model.ApiError{
Typ: model.ErrorInternal,
Err: errors.New("Found multiple org with same ID"),
}
}
if len(orgs) == 0 {
return nil, nil
}
return &orgs[0], nil
}
func (mds *ModelDaoSqlite) GetOrgByName(ctx context.Context,
name string) (*model.Organization, *model.ApiError) {
orgs := []model.Organization{}
if err := mds.db.Select(&orgs, `SELECT * FROM organizations WHERE name=?;`, name); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
if len(orgs) > 1 {
return nil, &model.ApiError{
Typ: model.ErrorInternal,
Err: errors.New("Multiple orgs with same ID found"),
}
}
if len(orgs) == 0 {
return nil, nil
}
return &orgs[0], nil
}
func (mds *ModelDaoSqlite) GetOrgs(ctx context.Context) ([]model.Organization, *model.ApiError) {
orgs := []model.Organization{}
err := mds.db.Select(&orgs, `SELECT * FROM organizations`)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return orgs, nil
}
func (mds *ModelDaoSqlite) EditOrg(ctx context.Context, org *model.Organization) *model.ApiError {
q := `UPDATE organizations SET name=?,has_opted_updates=?,is_anonymous=? WHERE id=?;`
_, err := mds.db.ExecContext(ctx, q, org.Name, org.HasOptedUpdates, org.IsAnonymous, org.Id)
if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
telemetry.GetInstance().SetTelemetryAnonymous(org.IsAnonymous)
return nil
}
func (mds *ModelDaoSqlite) DeleteOrg(ctx context.Context, id string) *model.ApiError {
_, err := mds.db.ExecContext(ctx, `DELETE from organizations where id=?;`, id)
if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return nil
}
func (mds *ModelDaoSqlite) CreateUser(ctx context.Context,
user *model.User) (*model.User, *model.ApiError) {
_, err := mds.db.ExecContext(ctx,
`INSERT INTO users (id, name, email, password, created_at, profile_picture_url, group_id, org_id)
VALUES (?, ?, ?, ?, ?, ?, ?,?);`,
user.Id, user.Name, user.Email, user.Password, user.CreatedAt,
user.ProfilePirctureURL, user.GroupId, user.OrgId,
)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return user, nil
}
func (mds *ModelDaoSqlite) EditUser(ctx context.Context,
update *model.User) (*model.User, *model.ApiError) {
_, err := mds.db.ExecContext(ctx,
`UPDATE users SET name=?,org_id=?,email=? WHERE id=?;`, update.Name,
update.OrgId, update.Email, update.Id)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return update, nil
}
func (mds *ModelDaoSqlite) UpdateUserPassword(ctx context.Context, passwordHash,
userId string) *model.ApiError {
q := `UPDATE users SET password=? WHERE id=?;`
if _, err := mds.db.ExecContext(ctx, q, passwordHash, userId); err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return nil
}
func (mds *ModelDaoSqlite) UpdateUserGroup(ctx context.Context, userId, groupId string) *model.ApiError {
q := `UPDATE users SET group_id=? WHERE id=?;`
if _, err := mds.db.ExecContext(ctx, q, groupId, userId); err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return nil
}
func (mds *ModelDaoSqlite) DeleteUser(ctx context.Context, id string) *model.ApiError {
result, err := mds.db.ExecContext(ctx, `DELETE from users where id=?;`, id)
if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
affectedRows, err := result.RowsAffected()
if err != nil {
return &model.ApiError{Typ: model.ErrorExec, Err: err}
}
if affectedRows == 0 {
return &model.ApiError{
Typ: model.ErrorNotFound,
Err: fmt.Errorf("no user found with id: %s", id),
}
}
return nil
}
func (mds *ModelDaoSqlite) GetUser(ctx context.Context,
id string) (*model.UserPayload, *model.ApiError) {
users := []model.UserPayload{}
query := `select
u.id,
u.name,
u.email,
u.password,
u.created_at,
u.profile_picture_url,
u.org_id,
u.group_id,
g.name as role,
o.name as organization
from users u, groups g, organizations o
where
g.id=u.group_id and
o.id = u.org_id and
u.id=?;`
if err := mds.db.Select(&users, query, id); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
if len(users) > 1 {
return nil, &model.ApiError{
Typ: model.ErrorInternal,
Err: errors.New("Found multiple users with same ID"),
}
}
if len(users) == 0 {
return nil, nil
}
return &users[0], nil
}
func (mds *ModelDaoSqlite) GetUserByEmail(ctx context.Context,
email string) (*model.UserPayload, *model.ApiError) {
users := []model.UserPayload{}
query := `select
u.id,
u.name,
u.email,
u.password,
u.created_at,
u.profile_picture_url,
u.org_id,
u.group_id,
g.name as role,
o.name as organization
from users u, groups g, organizations o
where
g.id=u.group_id and
o.id = u.org_id and
u.email=?;`
if err := mds.db.Select(&users, query, email); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
if len(users) > 1 {
return nil, &model.ApiError{
Typ: model.ErrorInternal,
Err: errors.New("Found multiple users with same ID."),
}
}
if len(users) == 0 {
return nil, nil
}
return &users[0], nil
}
func (mds *ModelDaoSqlite) GetUsers(ctx context.Context) ([]model.UserPayload, *model.ApiError) {
users := []model.UserPayload{}
query := `select
u.id,
u.name,
u.email,
u.password,
u.created_at,
u.profile_picture_url,
u.org_id,
u.group_id,
g.name as role,
o.name as organization
from users u, groups g, organizations o
where
g.id = u.group_id and
o.id = u.org_id`
err := mds.db.Select(&users, query)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return users, nil
}
func (mds *ModelDaoSqlite) GetUsersByOrg(ctx context.Context,
orgId string) ([]model.UserPayload, *model.ApiError) {
users := []model.UserPayload{}
query := `select
u.id,
u.name,
u.email,
u.password,
u.created_at,
u.profile_picture_url,
u.org_id,
u.group_id,
g.name as role,
o.name as organization
from users u, groups g, organizations o
where
u.group_id = g.id and
u.org_id = o.id and
u.org_id=?;`
if err := mds.db.Select(&users, query, orgId); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return users, nil
}
func (mds *ModelDaoSqlite) GetUsersByGroup(ctx context.Context,
groupId string) ([]model.UserPayload, *model.ApiError) {
users := []model.UserPayload{}
query := `select
u.id,
u.name,
u.email,
u.password,
u.created_at,
u.profile_picture_url,
u.org_id,
u.group_id,
g.name as role,
o.name as organization
from users u, groups g, organizations o
where
u.group_id = g.id and
o.id = u.org_id and
u.group_id=?;`
if err := mds.db.Select(&users, query, groupId); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return users, nil
}
func (mds *ModelDaoSqlite) CreateGroup(ctx context.Context,
group *model.Group) (*model.Group, *model.ApiError) {
group.Id = uuid.NewString()
q := `INSERT INTO groups (id, name) VALUES (?, ?);`
if _, err := mds.db.ExecContext(ctx, q, group.Id, group.Name); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return group, nil
}
func (mds *ModelDaoSqlite) DeleteGroup(ctx context.Context, id string) *model.ApiError {
if _, err := mds.db.ExecContext(ctx, `DELETE from groups where id=?;`, id); err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return nil
}
func (mds *ModelDaoSqlite) GetGroup(ctx context.Context,
id string) (*model.Group, *model.ApiError) {
groups := []model.Group{}
if err := mds.db.Select(&groups, `SELECT id, name FROM groups WHERE id=?`, id); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
if len(groups) > 1 {
return nil, &model.ApiError{
Typ: model.ErrorInternal,
Err: errors.New("Found multiple groups with same ID."),
}
}
if len(groups) == 0 {
return nil, nil
}
return &groups[0], nil
}
func (mds *ModelDaoSqlite) GetGroupByName(ctx context.Context,
name string) (*model.Group, *model.ApiError) {
groups := []model.Group{}
if err := mds.db.Select(&groups, `SELECT id, name FROM groups WHERE name=?`, name); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
if len(groups) > 1 {
return nil, &model.ApiError{
Typ: model.ErrorInternal,
Err: errors.New("Found multiple groups with same name"),
}
}
if len(groups) == 0 {
return nil, nil
}
return &groups[0], nil
}
func (mds *ModelDaoSqlite) GetGroups(ctx context.Context) ([]model.Group, *model.ApiError) {
groups := []model.Group{}
if err := mds.db.Select(&groups, "SELECT * FROM groups"); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return groups, nil
}
func (mds *ModelDaoSqlite) CreateResetPasswordEntry(ctx context.Context,
req *model.ResetPasswordEntry) *model.ApiError {
q := `INSERT INTO reset_password_request (user_id, token) VALUES (?, ?);`
if _, err := mds.db.ExecContext(ctx, q, req.UserId, req.Token); err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return nil
}
func (mds *ModelDaoSqlite) DeleteResetPasswordEntry(ctx context.Context,
token string) *model.ApiError {
_, err := mds.db.ExecContext(ctx, `DELETE from reset_password_request where token=?;`, token)
if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return nil
}
func (mds *ModelDaoSqlite) GetResetPasswordEntry(ctx context.Context,
token string) (*model.ResetPasswordEntry, *model.ApiError) {
entries := []model.ResetPasswordEntry{}
q := `SELECT user_id,token FROM reset_password_request WHERE token=?;`
if err := mds.db.Select(&entries, q, token); err != nil {
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
if len(entries) > 1 {
return nil, &model.ApiError{Typ: model.ErrorInternal,
Err: errors.New("Multiple entries for reset token is found")}
}
if len(entries) == 0 {
return nil, nil
}
return &entries[0], nil
}

View File

@ -1,91 +0,0 @@
package sqlite
import (
"context"
"fmt"
"github.com/google/uuid"
"go.signoz.io/query-service/model"
"go.signoz.io/query-service/telemetry"
"go.uber.org/zap"
)
func (mds *ModelDaoSqlite) FetchUserPreference(ctx context.Context) (*model.UserPreferences, *model.ApiError) {
userPreferences := []model.UserPreferences{}
query := fmt.Sprintf("SELECT id, uuid, isAnonymous, hasOptedUpdates FROM user_preferences;")
err := mds.db.Select(&userPreferences, query)
if err != nil {
zap.S().Debug("Error in processing sql query: ", err)
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
// zap.S().Info(query)
if len(userPreferences) > 1 {
zap.S().Debug("Error in processing sql query: ", fmt.Errorf("more than 1 row in user_preferences found"))
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
if len(userPreferences) == 0 {
return nil, nil
}
return &userPreferences[0], nil
}
func (mds *ModelDaoSqlite) UpdateUserPreferece(ctx context.Context, userPreferences *model.UserPreferences) *model.ApiError {
tx, err := mds.db.Begin()
if err != nil {
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
userPreferencesFound, apiError := mds.FetchUserPreference(ctx)
if apiError != nil {
return apiError
}
stmt, err := tx.Prepare(`UPDATE user_preferences SET isAnonymous=$1, hasOptedUpdates=$2 WHERE id=$3;`)
defer stmt.Close()
if err != nil {
zap.S().Errorf("Error in preparing statement for INSERT to user_preferences\n", err)
tx.Rollback()
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
query_result, err := stmt.Exec(userPreferences.GetIsAnonymous(), userPreferences.GetHasOptedUpdate(), userPreferencesFound.GetId())
if err != nil {
zap.S().Errorf("Error in Executing prepared statement for INSERT to user_preferences\n", err)
tx.Rollback() // return an error too, we may want to wrap them
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
zap.S().Debug(query_result.RowsAffected())
zap.S().Debug(userPreferences.GetIsAnonymous(), userPreferences.GetHasOptedUpdate(), userPreferencesFound.GetId())
err = tx.Commit()
if err != nil {
zap.S().Errorf("Error in committing transaction for INSERT to user_preferences\n", err)
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
telemetry.GetInstance().SetTelemetryAnonymous(userPreferences.GetIsAnonymous())
return nil
}
func (mds *ModelDaoSqlite) CreateDefaultUserPreference(ctx context.Context) (*model.UserPreferences, *model.ApiError) {
uuid := uuid.New().String()
_, err := mds.db.ExecContext(ctx, `INSERT INTO user_preferences (uuid, isAnonymous, hasOptedUpdates) VALUES (?, 0, 1);`, uuid)
if err != nil {
zap.S().Errorf("Error in preparing statement for INSERT to user_preferences\n", err)
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
}
return mds.FetchUserPreference(ctx)
}

View File

@ -28,6 +28,7 @@ require (
)
require (
github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect
github.com/klauspost/cpuid v1.2.3 // indirect
github.com/minio/md5-simd v1.1.0 // indirect
github.com/minio/sha256-simd v0.1.1 // indirect
@ -42,6 +43,7 @@ require (
github.com/Azure/go-autorest v10.8.1+incompatible // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect
github.com/auth0/go-jwt-middleware v1.0.1
github.com/aws/aws-sdk-go v1.27.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
@ -55,6 +57,7 @@ require (
github.com/go-logfmt/logfmt v0.5.0 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang/glog v0.0.0-20210429001901-424d2337a529 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.2 // indirect
@ -64,7 +67,7 @@ require (
github.com/googleapis/gax-go/v2 v2.0.5 // indirect
github.com/googleapis/gnostic v0.2.3-0.20180520015035-48a0ecefe2e4 // indirect
github.com/gophercloud/gophercloud v0.0.0-20170607034829-caf34a65f602 // indirect
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
github.com/gosimple/unidecode v1.0.0 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
@ -104,7 +107,7 @@ require (
github.com/segmentio/backo-go v1.0.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
github.com/smartystreets/assertions v1.1.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/pflag v1.0.3 // indirect
github.com/stretchr/testify v1.7.1
@ -114,7 +117,7 @@ require (
go.opentelemetry.io/otel/trace v1.4.1 // indirect
go.uber.org/atomic v1.6.0 // indirect
go.uber.org/multierr v1.5.0 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
golang.org/x/net v0.0.0-20211013171255-e13a2654a71e // indirect
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
@ -125,7 +128,7 @@ require (
google.golang.org/api v0.51.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20211013025323-ce878158c4d4 // indirect
google.golang.org/grpc v1.41.0 // indirect
google.golang.org/grpc v1.41.0
google.golang.org/grpc/examples v0.0.0-20210803221256-6ba56c814be7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect

View File

@ -63,6 +63,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/auth0/go-jwt-middleware v1.0.1 h1:/fsQ4vRr4zod1wKReUH+0A3ySRjGiT9G34kypO/EKwI=
github.com/auth0/go-jwt-middleware v1.0.1/go.mod h1:YSeUX3z6+TF2H+7padiEqNJ73Zy9vXW72U//IgN0BIM=
github.com/aws/aws-sdk-go v1.13.44-0.20180507225419-00862f899353/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k=
github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
@ -109,6 +111,8 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
@ -135,6 +139,8 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v0.0.0-20210429001901-424d2337a529 h1:2voWjNECnrZRbfwXxHB1/j8wa6xdKn85B5NzgVL/pTU=
github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -224,11 +230,13 @@ github.com/googleapis/gnostic v0.2.3-0.20180520015035-48a0ecefe2e4 h1:Z09Qt6AGDt
github.com/googleapis/gnostic v0.2.3-0.20180520015035-48a0ecefe2e4/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gophercloud/gophercloud v0.0.0-20170607034829-caf34a65f602 h1:Acc1d6mIuURCyYN6nkm1d7+Gycfq1+jUWdnBbTyGb6E=
github.com/gophercloud/gophercloud v0.0.0-20170607034829-caf34a65f602/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@ -396,8 +404,9 @@ github.com/shurcooL/vfsgen v0.0.0-20180711163814-62bca832be04/go.mod h1:TrYk7fJV
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0=
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
@ -418,6 +427,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

View File

@ -1,11 +1,13 @@
package main
import (
"context"
"os"
"os/signal"
"syscall"
"go.signoz.io/query-service/app"
"go.signoz.io/query-service/auth"
"go.signoz.io/query-service/constants"
"go.signoz.io/query-service/version"
@ -39,6 +41,15 @@ func main() {
// DruidClientUrl: constants.DruidClientUrl,
}
// Read the jwt secret key
auth.JwtSecret = os.Getenv("SIGNOZ_JWT_SECRET")
if len(auth.JwtSecret) == 0 {
zap.S().Warn("No JWT secret key is specified.")
} else {
zap.S().Info("No JWT secret key set successfully.")
}
server, err := app.NewServer(serverOptions)
if err != nil {
logger.Fatal("Failed to create server", zap.Error(err))
@ -48,6 +59,10 @@ func main() {
logger.Fatal("Could not start servers", zap.Error(err))
}
if err := auth.InitAuthCache(context.Background()); err != nil {
logger.Fatal("Failed to initialize auth cache", zap.Error(err))
}
signalsChannel := make(chan os.Signal, 1)
signal.Notify(signalsChannel, os.Interrupt, syscall.SIGTERM)

View File

@ -0,0 +1,51 @@
package model
type InviteRequest struct {
Name string `json:"name"`
Email string `json:"email"`
Role string `json:"role"`
}
type InviteResponse struct {
Email string `json:"email"`
InviteToken string `json:"inviteToken"`
}
type InvitationResponseObject struct {
Email string `json:"email" db:"email"`
Name string `json:"name" db:"name"`
Token string `json:"token" db:"token"`
CreatedAt int64 `json:"createdAt" db:"created_at"`
Role string `json:"role" db:"role"`
Organization string `json:"organization" db:"organization"`
}
type LoginRequest struct {
Email string `json:"email"`
Password string `json:"password"`
RefreshToken string `json:"refreshToken"`
}
type LoginResponse struct {
AccessJwt string `json:"accessJwt"`
AccessJwtExpiry int64 `json:"accessJwtExpiry"`
RefreshJwt string `json:"refreshJwt"`
RefreshJwtExpiry int64 `json:"refreshJwtExpiry"`
UserId string `json:"userId"`
}
type ChangePasswordRequest struct {
UserId string `json:"userId"`
OldPassword string `json:"oldPassword"`
NewPassword string `json:"newPassword"`
}
type ResetPasswordEntry struct {
UserId string `json:"userId" db:"user_id"`
Token string `json:"token" db:"token"`
}
type UserRole struct {
UserId string `json:"user_id"`
GroupName string `json:"group_name"`
}

View File

@ -0,0 +1,46 @@
package model
type Organization struct {
Id string `json:"id" db:"id"`
Name string `json:"name" db:"name"`
CreatedAt int64 `json:"createdAt" db:"created_at"`
IsAnonymous bool `json:"isAnonymous" db:"is_anonymous"`
HasOptedUpdates bool `json:"hasOptedUpdates" db:"has_opted_updates"`
}
type InvitationObject struct {
Id string `json:"id" db:"id"`
Email string `json:"email" db:"email"`
Name string `json:"name" db:"name"`
Token string `json:"token" db:"token"`
CreatedAt int64 `json:"createdAt" db:"created_at"`
Role string `json:"role" db:"role"`
OrgId string `json:"orgId" db:"org_id"`
}
type User struct {
Id string `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Email string `json:"email" db:"email"`
Password string `json:"password,omitempty" db:"password"`
CreatedAt int64 `json:"createdAt" db:"created_at"`
ProfilePirctureURL string `json:"profilePictureURL" db:"profile_picture_url"`
OrgId string `json:"orgId,omitempty" db:"org_id"`
GroupId string `json:"groupId,omitempty" db:"group_id"`
}
type UserPayload struct {
User
Role string `json:"role"`
Organization string `json:"organization"`
}
type Group struct {
Id string `json:"id" db:"id"`
Name string `json:"name" db:"name"`
}
type ResetPasswordRequest struct {
Password string `json:"password"`
Token string `json:"token"`
}

View File

@ -4,12 +4,6 @@ import (
"time"
)
type User struct {
Name string `json:"name"`
Email string `json:"email"`
OrganizationName string `json:"organizationName"`
}
type InstantQueryMetricsParams struct {
Time time.Time
Query string

View File

@ -27,6 +27,7 @@ const (
ErrorUnavailable ErrorType = "unavailable"
ErrorNotFound ErrorType = "not_found"
ErrorNotImplemented ErrorType = "not_implemented"
ErrorUnauthorized ErrorType = "unauthorized"
)
type QueryDataV2 struct {

View File

@ -1,27 +0,0 @@
package model
type UserPreferences struct {
Id int `json:"id" db:"id"`
Uuid string `json:"uuid" db:"uuid"`
IsAnonymous bool `json:"isAnonymous" db:"isAnonymous"`
HasOptedUpdates bool `json:"hasOptedUpdates" db:"hasOptedUpdates"`
}
func (up *UserPreferences) SetIsAnonymous(isAnonymous bool) {
up.IsAnonymous = isAnonymous
}
func (up *UserPreferences) SetHasOptedUpdate(hasOptedUpdates bool) {
up.HasOptedUpdates = hasOptedUpdates
}
func (up *UserPreferences) GetIsAnonymous() bool {
return up.IsAnonymous
}
func (up *UserPreferences) GetHasOptedUpdate() bool {
return up.HasOptedUpdates
}
func (up *UserPreferences) GetId() int {
return up.Id
}
func (up *UserPreferences) GetUUID() string {
return up.Uuid
}

View File

@ -0,0 +1,125 @@
package tests
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"go.signoz.io/query-service/auth"
"go.signoz.io/query-service/model"
)
func invite(t *testing.T, email string) *model.InviteResponse {
q := endpoint + fmt.Sprintf("/api/v1/invite?email=%s", email)
resp, err := client.Get(q)
require.NoError(t, err)
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)
var inviteResp model.InviteResponse
err = json.Unmarshal(b, &inviteResp)
require.NoError(t, err)
return &inviteResp
}
func register(email, password, token string) (string, error) {
q := endpoint + fmt.Sprintf("/api/v1/register")
req := auth.RegisterRequest{
Email: email,
Password: password,
InviteToken: token,
}
b, err := json.Marshal(req)
if err != nil {
return "", err
}
resp, err := client.Post(q, "application/json", bytes.NewBuffer(b))
if err != nil {
return "", err
}
defer resp.Body.Close()
b, err = ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(b), nil
}
func login(email, password, refreshToken string) (*model.LoginResponse, error) {
q := endpoint + fmt.Sprintf("/api/v1/login")
req := model.LoginRequest{
Email: email,
Password: password,
RefreshToken: refreshToken,
}
b, err := json.Marshal(req)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal")
}
resp, err := client.Post(q, "application/json", bytes.NewBuffer(b))
if err != nil {
return nil, errors.Wrap(err, "failed to post")
}
defer resp.Body.Close()
b, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrap(err, "failed to read body")
}
loginResp := &model.LoginResponse{}
err = json.Unmarshal(b, loginResp)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal")
}
return loginResp, nil
}
func TestAuthInviteAPI(t *testing.T) {
t.Skip()
email := "abc@signoz.io"
resp := invite(t, email)
require.Equal(t, email, resp.Email)
require.NotNil(t, resp.InviteToken)
}
func TestAuthRegisterAPI(t *testing.T) {
email := "alice@signoz.io"
resp, err := register(email, "password", "")
require.NoError(t, err)
require.Contains(t, resp, "user registered successfully")
}
func TestAuthLoginAPI(t *testing.T) {
t.Skip()
email := "abc-login@signoz.io"
password := "password123"
inv := invite(t, email)
resp, err := register(email, password, inv.InviteToken)
require.NoError(t, err)
require.Contains(t, resp, "user registered successfully")
loginResp, err := login(email, password, "")
require.NoError(t, err)
loginResp2, err := login("", "", loginResp.RefreshJwt)
require.NoError(t, err)
require.NotNil(t, loginResp2.AccessJwt)
}

View File

@ -16,6 +16,9 @@ services:
interval: 30s
timeout: 5s
retries: 3
ports:
- "9000:9000"
- "8123:8123"
alertmanager:
image: signoz/alertmanager:0.6.0