mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-16 08:15:54 +08:00
feat(auth): Add auth and access-controls support (#874)
* auth and rbac support enabled
This commit is contained in:
parent
3ef9d96678
commit
6ccdc5296e
2
.gitignore
vendored
2
.gitignore
vendored
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
373
pkg/query-service/auth/auth.go
Normal file
373
pkg/query-service/auth/auth.go
Normal 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
|
||||
}
|
94
pkg/query-service/auth/jwt.go
Normal file
94
pkg/query-service/auth/jwt.go
Normal 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)
|
||||
}
|
136
pkg/query-service/auth/rbac.go
Normal file
136
pkg/query-service/auth/rbac.go
Normal 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
|
||||
}
|
55
pkg/query-service/auth/utils.go
Normal file
55
pkg/query-service/auth/utils.go
Normal 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
|
||||
}
|
7
pkg/query-service/constants/auth.go
Normal file
7
pkg/query-service/constants/auth.go
Normal file
@ -0,0 +1,7 @@
|
||||
package constants
|
||||
|
||||
const (
|
||||
AdminGroup = "ADMIN"
|
||||
EditorGroup = "EDITOR"
|
||||
ViewerGroup = "VIEWER"
|
||||
)
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
56
pkg/query-service/dao/interface.go
Normal file
56
pkg/query-service/dao/interface.go
Normal 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
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package interfaces
|
||||
|
||||
type ModelDao interface {
|
||||
UserPreferenceDao
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
||||
|
512
pkg/query-service/dao/sqlite/rbac.go
Normal file
512
pkg/query-service/dao/sqlite/rbac.go
Normal 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
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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=
|
||||
|
@ -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)
|
||||
|
||||
|
51
pkg/query-service/model/auth.go
Normal file
51
pkg/query-service/model/auth.go
Normal 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"`
|
||||
}
|
46
pkg/query-service/model/db.go
Normal file
46
pkg/query-service/model/db.go
Normal 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"`
|
||||
}
|
@ -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
|
||||
|
@ -27,6 +27,7 @@ const (
|
||||
ErrorUnavailable ErrorType = "unavailable"
|
||||
ErrorNotFound ErrorType = "not_found"
|
||||
ErrorNotImplemented ErrorType = "not_implemented"
|
||||
ErrorUnauthorized ErrorType = "unauthorized"
|
||||
)
|
||||
|
||||
type QueryDataV2 struct {
|
||||
|
@ -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
|
||||
}
|
125
pkg/query-service/tests/auth_test.go
Normal file
125
pkg/query-service/tests/auth_test.go
Normal 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)
|
||||
}
|
Binary file not shown.
@ -16,6 +16,9 @@ services:
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "8123:8123"
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:0.6.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user