diff --git a/ee/query-service/app/api/api.go b/ee/query-service/app/api/api.go index 22f3ee67c2..32bb22435f 100644 --- a/ee/query-service/app/api/api.go +++ b/ee/query-service/app/api/api.go @@ -152,9 +152,10 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew router.HandleFunc("/api/v2/metrics/query_range", am.ViewAccess(ah.queryRangeMetricsV2)).Methods(http.MethodPost) // PAT APIs - router.HandleFunc("/api/v1/pat", am.AdminAccess(ah.createPAT)).Methods(http.MethodPost) - router.HandleFunc("/api/v1/pat", am.AdminAccess(ah.getPATs)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/pat/{id}", am.AdminAccess(ah.deletePAT)).Methods(http.MethodDelete) + router.HandleFunc("/api/v1/pats", am.AdminAccess(ah.createPAT)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/pats", am.AdminAccess(ah.getPATs)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/pats/{id}", am.AdminAccess(ah.updatePAT)).Methods(http.MethodPut) + router.HandleFunc("/api/v1/pats/{id}", am.AdminAccess(ah.revokePAT)).Methods(http.MethodDelete) router.HandleFunc("/api/v1/checkout", am.AdminAccess(ah.checkout)).Methods(http.MethodPost) router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet) diff --git a/ee/query-service/app/api/pat.go b/ee/query-service/app/api/pat.go index b0fcf073a4..49ed36f092 100644 --- a/ee/query-service/app/api/pat.go +++ b/ee/query-service/app/api/pat.go @@ -12,6 +12,7 @@ import ( "github.com/gorilla/mux" "go.signoz.io/signoz/ee/query-service/model" "go.signoz.io/signoz/pkg/query-service/auth" + baseconstants "go.signoz.io/signoz/pkg/query-service/constants" basemodel "go.signoz.io/signoz/pkg/query-service/model" "go.uber.org/zap" ) @@ -28,7 +29,7 @@ func generatePATToken() string { func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) { ctx := context.Background() - req := model.PAT{} + req := model.CreatePATRequestBody{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { RespondError(w, model.BadRequest(err), nil) return @@ -41,30 +42,87 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) { }, nil) return } + pat := model.PAT{ + Name: req.Name, + Role: req.Role, + ExpiresAt: req.ExpiresInDays, + } + err = validatePATRequest(pat) + if err != nil { + RespondError(w, model.BadRequest(err), nil) + return + } + + // All the PATs are associated with the user creating the PAT. + pat.UserID = user.Id + pat.CreatedAt = time.Now().Unix() + pat.UpdatedAt = time.Now().Unix() + pat.LastUsed = 0 + pat.Token = generatePATToken() + + if pat.ExpiresAt != 0 { + // convert expiresAt to unix timestamp from days + pat.ExpiresAt = time.Now().Unix() + (pat.ExpiresAt * 24 * 60 * 60) + } + + zap.S().Debugf("Got Create PAT request: %+v", pat) + var apierr basemodel.BaseApiError + if pat, apierr = ah.AppDao().CreatePAT(ctx, pat); apierr != nil { + RespondError(w, apierr, nil) + return + } + + ah.Respond(w, &pat) +} + +func validatePATRequest(req model.PAT) error { + if req.Role == "" || (req.Role != baseconstants.ViewerGroup && req.Role != baseconstants.EditorGroup && req.Role != baseconstants.AdminGroup) { + return fmt.Errorf("valid role is required") + } + if req.ExpiresAt < 0 { + return fmt.Errorf("valid expiresAt is required") + } + if req.Name == "" { + return fmt.Errorf("valid name is required") + } + return nil +} + +func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) { + ctx := context.Background() - // All the PATs are associated with the user creating the PAT. Hence, the permissions - // associated with the PAT is also equivalent to that of the user. - req.UserID = user.Id - req.CreatedAt = time.Now().Unix() - req.Token = generatePATToken() + req := model.PAT{} + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + RespondError(w, model.BadRequest(err), nil) + return + } - // default expiry is 30 days - if req.ExpiresAt == 0 { - req.ExpiresAt = time.Now().AddDate(0, 0, 30).Unix() + user, err := auth.GetUserFromRequest(r) + if err != nil { + RespondError(w, &model.ApiError{ + Typ: model.ErrorUnauthorized, + Err: err, + }, nil) + return } - // max expiry is 1 year - if req.ExpiresAt > time.Now().AddDate(1, 0, 0).Unix() { - req.ExpiresAt = time.Now().AddDate(1, 0, 0).Unix() + + err = validatePATRequest(req) + if err != nil { + RespondError(w, model.BadRequest(err), nil) + return } - zap.S().Debugf("Got PAT request: %+v", req) + req.UpdatedByUserID = user.Id + id := mux.Vars(r)["id"] + req.UpdatedAt = time.Now().Unix() + zap.S().Debugf("Got Update PAT request: %+v", req) var apierr basemodel.BaseApiError - if req, apierr = ah.AppDao().CreatePAT(ctx, req); apierr != nil { + if apierr = ah.AppDao().UpdatePAT(ctx, req, id); apierr != nil { RespondError(w, apierr, nil) return } - ah.Respond(w, &req) + ah.Respond(w, map[string]string{"data": "pat updated successfully"}) } func (ah *APIHandler) getPATs(w http.ResponseWriter, r *http.Request) { @@ -86,7 +144,7 @@ func (ah *APIHandler) getPATs(w http.ResponseWriter, r *http.Request) { ah.Respond(w, pats) } -func (ah *APIHandler) deletePAT(w http.ResponseWriter, r *http.Request) { +func (ah *APIHandler) revokePAT(w http.ResponseWriter, r *http.Request) { ctx := context.Background() id := mux.Vars(r)["id"] user, err := auth.GetUserFromRequest(r) @@ -105,14 +163,14 @@ func (ah *APIHandler) deletePAT(w http.ResponseWriter, r *http.Request) { if pat.UserID != user.Id { RespondError(w, &model.ApiError{ Typ: model.ErrorUnauthorized, - Err: fmt.Errorf("unauthorized PAT delete request"), + Err: fmt.Errorf("unauthorized PAT revoke request"), }, nil) return } - zap.S().Debugf("Delete PAT with id: %+v", id) - if apierr := ah.AppDao().DeletePAT(ctx, id); apierr != nil { + zap.S().Debugf("Revoke PAT with id: %+v", id) + if apierr := ah.AppDao().RevokePAT(ctx, id, user.Id); apierr != nil { RespondError(w, apierr, nil) return } - ah.Respond(w, map[string]string{"data": "pat deleted successfully"}) + ah.Respond(w, map[string]string{"data": "pat revoked successfully"}) } diff --git a/ee/query-service/app/server.go b/ee/query-service/app/server.go index ea0b0344ad..f8c7633417 100644 --- a/ee/query-service/app/server.go +++ b/ee/query-service/app/server.go @@ -20,10 +20,11 @@ import ( "github.com/soheilhy/cmux" "go.signoz.io/signoz/ee/query-service/app/api" "go.signoz.io/signoz/ee/query-service/app/db" + "go.signoz.io/signoz/ee/query-service/auth" "go.signoz.io/signoz/ee/query-service/constants" "go.signoz.io/signoz/ee/query-service/dao" "go.signoz.io/signoz/ee/query-service/interfaces" - "go.signoz.io/signoz/pkg/query-service/auth" + baseauth "go.signoz.io/signoz/pkg/query-service/auth" baseInterface "go.signoz.io/signoz/pkg/query-service/interfaces" v3 "go.signoz.io/signoz/pkg/query-service/model/v3" @@ -37,7 +38,6 @@ import ( "go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline" "go.signoz.io/signoz/pkg/query-service/app/opamp" opAmpModel "go.signoz.io/signoz/pkg/query-service/app/opamp/model" - baseauth "go.signoz.io/signoz/pkg/query-service/auth" "go.signoz.io/signoz/pkg/query-service/cache" baseconst "go.signoz.io/signoz/pkg/query-service/constants" "go.signoz.io/signoz/pkg/query-service/healthcheck" @@ -304,25 +304,12 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, e r := mux.NewRouter() + // add auth middleware getUserFromRequest := func(r *http.Request) (*basemodel.UserPayload, error) { - patToken := r.Header.Get("SIGNOZ-API-KEY") - if len(patToken) > 0 { - zap.S().Debugf("Received a non-zero length PAT token") - ctx := context.Background() - dao := apiHandler.AppDao() - - user, err := dao.GetUserByPAT(ctx, patToken) - if err == nil && user != nil { - zap.S().Debugf("Found valid PAT user: %+v", user) - return user, nil - } - if err != nil { - zap.S().Debugf("Error while getting user for PAT: %+v", err) - } - } - return baseauth.GetUserFromRequest(r) + return auth.GetUserFromRequest(r, apiHandler) } am := baseapp.NewAuthMiddleware(getUserFromRequest) + r.Use(setTimeoutMiddleware) r.Use(s.analyticsMiddleware) r.Use(loggingMiddleware) @@ -439,7 +426,7 @@ func extractQueryRangeV3Data(path string, r *http.Request) (map[string]interface telemetry.GetInstance().AddActiveLogsUser() } data["dataSources"] = dataSources - userEmail, err := auth.GetEmailFromJwt(r.Context()) + userEmail, err := baseauth.GetEmailFromJwt(r.Context()) if err == nil { telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_QUERY_RANGE_V3, data, userEmail, true) } @@ -463,7 +450,7 @@ func getActiveLogs(path string, r *http.Request) { func (s *Server) analyticsMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := auth.AttachJwtToContext(r.Context(), r) + ctx := baseauth.AttachJwtToContext(r.Context(), r) r = r.WithContext(ctx) route := mux.CurrentRoute(r) path, _ := route.GetPathTemplate() @@ -482,7 +469,7 @@ func (s *Server) analyticsMiddleware(next http.Handler) http.Handler { } if _, ok := telemetry.EnabledPaths()[path]; ok { - userEmail, err := auth.GetEmailFromJwt(r.Context()) + userEmail, err := baseauth.GetEmailFromJwt(r.Context()) if err == nil { telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_PATH, data, userEmail) } diff --git a/ee/query-service/auth/auth.go b/ee/query-service/auth/auth.go new file mode 100644 index 0000000000..8c06384549 --- /dev/null +++ b/ee/query-service/auth/auth.go @@ -0,0 +1,56 @@ +package auth + +import ( + "context" + "fmt" + "net/http" + "time" + + "go.signoz.io/signoz/ee/query-service/app/api" + baseauth "go.signoz.io/signoz/pkg/query-service/auth" + basemodel "go.signoz.io/signoz/pkg/query-service/model" + "go.signoz.io/signoz/pkg/query-service/telemetry" + + "go.uber.org/zap" +) + +func GetUserFromRequest(r *http.Request, apiHandler *api.APIHandler) (*basemodel.UserPayload, error) { + patToken := r.Header.Get("SIGNOZ-API-KEY") + if len(patToken) > 0 { + zap.S().Debugf("Received a non-zero length PAT token") + ctx := context.Background() + dao := apiHandler.AppDao() + + pat, err := dao.GetPAT(ctx, patToken) + if err == nil && pat != nil { + zap.S().Debugf("Found valid PAT: %+v", pat) + if pat.ExpiresAt < time.Now().Unix() && pat.ExpiresAt != 0 { + zap.S().Debugf("PAT has expired: %+v", pat) + return nil, fmt.Errorf("PAT has expired") + } + group, apiErr := dao.GetGroupByName(ctx, pat.Role) + if apiErr != nil { + zap.S().Debugf("Error while getting group for PAT: %+v", apiErr) + return nil, apiErr + } + user, err := dao.GetUser(ctx, pat.UserID) + if err != nil { + zap.S().Debugf("Error while getting user for PAT: %+v", err) + return nil, err + } + telemetry.GetInstance().SetPatTokenUser() + dao.UpdatePATLastUsed(ctx, patToken, time.Now().Unix()) + user.User.GroupId = group.Id + user.User.Id = pat.Id + return &basemodel.UserPayload{ + User: user.User, + Role: pat.Role, + }, nil + } + if err != nil { + zap.S().Debugf("Error while getting user for PAT: %+v", err) + return nil, err + } + } + return baseauth.GetUserFromRequest(r) +} diff --git a/ee/query-service/dao/interface.go b/ee/query-service/dao/interface.go index 479ca56edc..78155bc23a 100644 --- a/ee/query-service/dao/interface.go +++ b/ee/query-service/dao/interface.go @@ -34,9 +34,11 @@ type ModelDao interface { GetDomainByEmail(ctx context.Context, email string) (*model.OrgDomain, basemodel.BaseApiError) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basemodel.BaseApiError) + UpdatePAT(ctx context.Context, p model.PAT, id string) (basemodel.BaseApiError) GetPAT(ctx context.Context, pat string) (*model.PAT, basemodel.BaseApiError) + UpdatePATLastUsed(ctx context.Context, pat string, lastUsed int64) basemodel.BaseApiError GetPATByID(ctx context.Context, id string) (*model.PAT, basemodel.BaseApiError) GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError) ListPATs(ctx context.Context, userID string) ([]model.PAT, basemodel.BaseApiError) - DeletePAT(ctx context.Context, id string) basemodel.BaseApiError + RevokePAT(ctx context.Context, id string, userID string) basemodel.BaseApiError } diff --git a/ee/query-service/dao/sqlite/modelDao.go b/ee/query-service/dao/sqlite/modelDao.go index 3c195ea9bf..02b4367da0 100644 --- a/ee/query-service/dao/sqlite/modelDao.go +++ b/ee/query-service/dao/sqlite/modelDao.go @@ -7,6 +7,7 @@ import ( basedao "go.signoz.io/signoz/pkg/query-service/dao" basedsql "go.signoz.io/signoz/pkg/query-service/dao/sqlite" baseint "go.signoz.io/signoz/pkg/query-service/interfaces" + "go.uber.org/zap" ) type modelDao struct { @@ -28,6 +29,41 @@ func (m *modelDao) checkFeature(key string) error { return m.flags.CheckFeature(key) } +func columnExists(db *sqlx.DB, tableName, columnName string) bool { + query := fmt.Sprintf("PRAGMA table_info(%s);", tableName) + rows, err := db.Query(query) + if err != nil { + zap.L().Error("Failed to query table info", zap.Error(err)) + return false + } + defer rows.Close() + + var ( + cid int + name string + ctype string + notnull int + dflt_value *string + pk int + ) + for rows.Next() { + err := rows.Scan(&cid, &name, &ctype, ¬null, &dflt_value, &pk) + if err != nil { + zap.L().Error("Failed to scan table info", zap.Error(err)) + return false + } + if name == columnName { + return true + } + } + err = rows.Err() + if err != nil { + zap.L().Error("Failed to scan table info", zap.Error(err)) + return false + } + return false +} + // InitDB creates and extends base model DB repository func InitDB(dataSourceName string) (*modelDao, error) { dao, err := basedsql.InitDB(dataSourceName) @@ -51,11 +87,16 @@ func InitDB(dataSourceName string) (*modelDao, error) { ); CREATE TABLE IF NOT EXISTS personal_access_tokens ( id INTEGER PRIMARY KEY AUTOINCREMENT, + role TEXT NOT NULL, user_id TEXT NOT NULL, token TEXT NOT NULL UNIQUE, name TEXT NOT NULL, created_at INTEGER NOT NULL, expires_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + last_used INTEGER NOT NULL, + revoked BOOLEAN NOT NULL, + updated_by_user_id TEXT NOT NULL, FOREIGN KEY(user_id) REFERENCES users(id) ); ` @@ -65,6 +106,36 @@ func InitDB(dataSourceName string) (*modelDao, error) { return nil, fmt.Errorf("error in creating tables: %v", err.Error()) } + if !columnExists(m.DB(), "personal_access_tokens", "role") { + _, err = m.DB().Exec("ALTER TABLE personal_access_tokens ADD COLUMN role TEXT NOT NULL DEFAULT 'ADMIN';") + if err != nil { + return nil, fmt.Errorf("error in adding column: %v", err.Error()) + } + } + if !columnExists(m.DB(), "personal_access_tokens", "updated_at") { + _, err = m.DB().Exec("ALTER TABLE personal_access_tokens ADD COLUMN updated_at INTEGER NOT NULL DEFAULT 0;") + if err != nil { + return nil, fmt.Errorf("error in adding column: %v", err.Error()) + } + } + if !columnExists(m.DB(), "personal_access_tokens", "last_used") { + _, err = m.DB().Exec("ALTER TABLE personal_access_tokens ADD COLUMN last_used INTEGER NOT NULL DEFAULT 0;") + if err != nil { + return nil, fmt.Errorf("error in adding column: %v", err.Error()) + } + } + if !columnExists(m.DB(), "personal_access_tokens", "revoked") { + _, err = m.DB().Exec("ALTER TABLE personal_access_tokens ADD COLUMN revoked BOOLEAN NOT NULL DEFAULT FALSE;") + if err != nil { + return nil, fmt.Errorf("error in adding column: %v", err.Error()) + } + } + if !columnExists(m.DB(), "personal_access_tokens", "updated_by_user_id") { + _, err = m.DB().Exec("ALTER TABLE personal_access_tokens ADD COLUMN updated_by_user_id TEXT NOT NULL DEFAULT '';") + if err != nil { + return nil, fmt.Errorf("error in adding column: %v", err.Error()) + } + } return m, nil } diff --git a/ee/query-service/dao/sqlite/pat.go b/ee/query-service/dao/sqlite/pat.go index 5bd1b78a62..a1752ea238 100644 --- a/ee/query-service/dao/sqlite/pat.go +++ b/ee/query-service/dao/sqlite/pat.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strconv" + "time" "go.signoz.io/signoz/ee/query-service/model" basemodel "go.signoz.io/signoz/pkg/query-service/model" @@ -12,12 +13,16 @@ import ( func (m *modelDao) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basemodel.BaseApiError) { result, err := m.DB().ExecContext(ctx, - "INSERT INTO personal_access_tokens (user_id, token, name, created_at, expires_at) VALUES ($1, $2, $3, $4, $5)", + "INSERT INTO personal_access_tokens (user_id, token, role, name, created_at, expires_at, updated_at, updated_by_user_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", p.UserID, p.Token, + p.Role, p.Name, p.CreatedAt, - p.ExpiresAt) + p.ExpiresAt, + p.UpdatedAt, + p.UpdatedByUserID, + ) if err != nil { zap.S().Errorf("Failed to insert PAT in db, err: %v", zap.Error(err)) return model.PAT{}, model.InternalError(fmt.Errorf("PAT insertion failed")) @@ -28,24 +33,102 @@ func (m *modelDao) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basem return model.PAT{}, model.InternalError(fmt.Errorf("PAT insertion failed")) } p.Id = strconv.Itoa(int(id)) + createdByUser, _ := m.GetUser(ctx, p.UserID) + if createdByUser == nil { + p.CreatedByUser = model.User{ + NotFound: true, + } + } else { + p.CreatedByUser = model.User{ + Id: createdByUser.Id, + Name: createdByUser.Name, + Email: createdByUser.Email, + CreatedAt: createdByUser.CreatedAt, + ProfilePictureURL: createdByUser.ProfilePictureURL, + NotFound: false, + } + } return p, nil } +func (m *modelDao) UpdatePAT(ctx context.Context, p model.PAT, id string) basemodel.BaseApiError { + _, err := m.DB().ExecContext(ctx, + "UPDATE personal_access_tokens SET role=$1, name=$2, updated_at=$3, updated_by_user_id=$4 WHERE id=$5 and revoked=false;", + p.Role, + p.Name, + p.UpdatedAt, + p.UpdatedByUserID, + id) + if err != nil { + zap.S().Errorf("Failed to update PAT in db, err: %v", zap.Error(err)) + return model.InternalError(fmt.Errorf("PAT update failed")) + } + return nil +} + +func (m *modelDao) UpdatePATLastUsed(ctx context.Context, token string, lastUsed int64) basemodel.BaseApiError { + _, err := m.DB().ExecContext(ctx, + "UPDATE personal_access_tokens SET last_used=$1 WHERE token=$2 and revoked=false;", + lastUsed, + token) + if err != nil { + zap.S().Errorf("Failed to update PAT last used in db, err: %v", zap.Error(err)) + return model.InternalError(fmt.Errorf("PAT last used update failed")) + } + return nil +} + func (m *modelDao) ListPATs(ctx context.Context, userID string) ([]model.PAT, basemodel.BaseApiError) { pats := []model.PAT{} - if err := m.DB().Select(&pats, `SELECT * FROM personal_access_tokens WHERE user_id=?;`, userID); err != nil { + if err := m.DB().Select(&pats, `SELECT * FROM personal_access_tokens WHERE user_id=? and revoked=false ORDER by updated_at DESC;`, userID); err != nil { zap.S().Errorf("Failed to fetch PATs for user: %s, err: %v", userID, zap.Error(err)) return nil, model.InternalError(fmt.Errorf("failed to fetch PATs")) } + for i := range pats { + createdByUser, _ := m.GetUser(ctx, pats[i].UserID) + if createdByUser == nil { + pats[i].CreatedByUser = model.User{ + NotFound: true, + } + } else { + pats[i].CreatedByUser = model.User{ + Id: createdByUser.Id, + Name: createdByUser.Name, + Email: createdByUser.Email, + CreatedAt: createdByUser.CreatedAt, + ProfilePictureURL: createdByUser.ProfilePictureURL, + NotFound: false, + } + } + + updatedByUser, _ := m.GetUser(ctx, pats[i].UpdatedByUserID) + if updatedByUser == nil { + pats[i].UpdatedByUser = model.User{ + NotFound: true, + } + } else { + pats[i].UpdatedByUser = model.User{ + Id: updatedByUser.Id, + Name: updatedByUser.Name, + Email: updatedByUser.Email, + CreatedAt: updatedByUser.CreatedAt, + ProfilePictureURL: updatedByUser.ProfilePictureURL, + NotFound: false, + } + } + } return pats, nil } -func (m *modelDao) DeletePAT(ctx context.Context, id string) basemodel.BaseApiError { - _, err := m.DB().ExecContext(ctx, `DELETE from personal_access_tokens where id=?;`, id) +func (m *modelDao) RevokePAT(ctx context.Context, id string, userID string) basemodel.BaseApiError { + updatedAt := time.Now().Unix() + _, err := m.DB().ExecContext(ctx, + "UPDATE personal_access_tokens SET revoked=true, updated_by_user_id = $1, updated_at=$2 WHERE id=$3", + userID, updatedAt, id) if err != nil { - zap.S().Errorf("Failed to delete PAT, err: %v", zap.Error(err)) - return model.InternalError(fmt.Errorf("failed to delete PAT")) + zap.S().Errorf("Failed to revoke PAT in db, err: %v", zap.Error(err)) + return model.InternalError(fmt.Errorf("PAT revoke failed")) } return nil } @@ -53,7 +136,7 @@ func (m *modelDao) DeletePAT(ctx context.Context, id string) basemodel.BaseApiEr func (m *modelDao) GetPAT(ctx context.Context, token string) (*model.PAT, basemodel.BaseApiError) { pats := []model.PAT{} - if err := m.DB().Select(&pats, `SELECT * FROM personal_access_tokens WHERE token=?;`, token); err != nil { + if err := m.DB().Select(&pats, `SELECT * FROM personal_access_tokens WHERE token=? and revoked=false;`, token); err != nil { return nil, model.InternalError(fmt.Errorf("failed to fetch PAT")) } @@ -70,7 +153,7 @@ func (m *modelDao) GetPAT(ctx context.Context, token string) (*model.PAT, basemo func (m *modelDao) GetPATByID(ctx context.Context, id string) (*model.PAT, basemodel.BaseApiError) { pats := []model.PAT{} - if err := m.DB().Select(&pats, `SELECT * FROM personal_access_tokens WHERE id=?;`, id); err != nil { + if err := m.DB().Select(&pats, `SELECT * FROM personal_access_tokens WHERE id=? and revoked=false;`, id); err != nil { return nil, model.InternalError(fmt.Errorf("failed to fetch PAT")) } @@ -84,6 +167,7 @@ func (m *modelDao) GetPATByID(ctx context.Context, id string) (*model.PAT, basem return &pats[0], nil } +// deprecated func (m *modelDao) GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError) { users := []basemodel.UserPayload{} diff --git a/ee/query-service/model/pat.go b/ee/query-service/model/pat.go index f320d0be7c..ef683a08bf 100644 --- a/ee/query-service/model/pat.go +++ b/ee/query-service/model/pat.go @@ -1,10 +1,32 @@ package model +type User struct { + Id string `json:"id" db:"id"` + Name string `json:"name" db:"name"` + Email string `json:"email" db:"email"` + CreatedAt int64 `json:"createdAt" db:"created_at"` + ProfilePictureURL string `json:"profilePictureURL" db:"profile_picture_url"` + NotFound bool `json:"notFound"` +} + +type CreatePATRequestBody struct { + Name string `json:"name"` + Role string `json:"role"` + ExpiresInDays int64 `json:"expiresInDays"` +} + type PAT struct { - Id string `json:"id" db:"id"` - UserID string `json:"userId" db:"user_id"` - Token string `json:"token" db:"token"` - Name string `json:"name" db:"name"` - CreatedAt int64 `json:"createdAt" db:"created_at"` - ExpiresAt int64 `json:"expiresAt" db:"expires_at"` + Id string `json:"id" db:"id"` + UserID string `json:"userId" db:"user_id"` + CreatedByUser User `json:"createdByUser"` + UpdatedByUser User `json:"updatedByUser"` + Token string `json:"token" db:"token"` + Role string `json:"role" db:"role"` + Name string `json:"name" db:"name"` + CreatedAt int64 `json:"createdAt" db:"created_at"` + ExpiresAt int64 `json:"expiresAt" db:"expires_at"` + UpdatedAt int64 `json:"updatedAt" db:"updated_at"` + LastUsed int64 `json:"lastUsed" db:"last_used"` + Revoked bool `json:"revoked" db:"revoked"` + UpdatedByUserID string `json:"updatedByUserId" db:"updated_by_user_id"` } diff --git a/pkg/query-service/telemetry/telemetry.go b/pkg/query-service/telemetry/telemetry.go index 55b9a8a72b..a840760ac2 100644 --- a/pkg/query-service/telemetry/telemetry.go +++ b/pkg/query-service/telemetry/telemetry.go @@ -152,6 +152,7 @@ type Telemetry struct { maxRandInt int rateLimits map[string]int8 activeUser map[string]int8 + patTokenUser bool countUsers int8 mutex sync.RWMutex } @@ -243,7 +244,9 @@ func createTelemetry() { "metricsTTLStatus": metricsTTL.Status, "tracesTTLStatus": traceTTL.Status, "logsTTLStatus": logsTTL.Status, + "patUser": telemetry.patTokenUser, } + telemetry.patTokenUser = false for key, value := range tsInfo { data[key] = value } @@ -346,6 +349,10 @@ func (a *Telemetry) SetUserEmail(email string) { a.userEmail = email } +func (a *Telemetry) SetPatTokenUser() { + a.patTokenUser = true +} + func (a *Telemetry) GetUserEmail() string { return a.userEmail }