mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-10 07:19:01 +08:00
feat: api management (#4557)
* feat: api management * chore: address review comments and typos * chore: add sort and created by user object on create * chore: replace expiresAt with expiresInDays for request body
This commit is contained in:
parent
548c531956
commit
d11c1eb439
@ -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)
|
router.HandleFunc("/api/v2/metrics/query_range", am.ViewAccess(ah.queryRangeMetricsV2)).Methods(http.MethodPost)
|
||||||
|
|
||||||
// PAT APIs
|
// PAT APIs
|
||||||
router.HandleFunc("/api/v1/pat", am.AdminAccess(ah.createPAT)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v1/pats", am.AdminAccess(ah.createPAT)).Methods(http.MethodPost)
|
||||||
router.HandleFunc("/api/v1/pat", am.AdminAccess(ah.getPATs)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v1/pats", 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/{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/checkout", am.AdminAccess(ah.checkout)).Methods(http.MethodPost)
|
||||||
router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet)
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"go.signoz.io/signoz/ee/query-service/model"
|
"go.signoz.io/signoz/ee/query-service/model"
|
||||||
"go.signoz.io/signoz/pkg/query-service/auth"
|
"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"
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@ -28,7 +29,7 @@ func generatePATToken() string {
|
|||||||
func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
|
func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
req := model.PAT{}
|
req := model.CreatePATRequestBody{}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
RespondError(w, model.BadRequest(err), nil)
|
RespondError(w, model.BadRequest(err), nil)
|
||||||
return
|
return
|
||||||
@ -41,30 +42,87 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
|
|||||||
}, nil)
|
}, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
pat := model.PAT{
|
||||||
// All the PATs are associated with the user creating the PAT. Hence, the permissions
|
Name: req.Name,
|
||||||
// associated with the PAT is also equivalent to that of the user.
|
Role: req.Role,
|
||||||
req.UserID = user.Id
|
ExpiresAt: req.ExpiresInDays,
|
||||||
req.CreatedAt = time.Now().Unix()
|
|
||||||
req.Token = generatePATToken()
|
|
||||||
|
|
||||||
// default expiry is 30 days
|
|
||||||
if req.ExpiresAt == 0 {
|
|
||||||
req.ExpiresAt = time.Now().AddDate(0, 0, 30).Unix()
|
|
||||||
}
|
}
|
||||||
// max expiry is 1 year
|
err = validatePATRequest(pat)
|
||||||
if req.ExpiresAt > time.Now().AddDate(1, 0, 0).Unix() {
|
if err != nil {
|
||||||
req.ExpiresAt = time.Now().AddDate(1, 0, 0).Unix()
|
RespondError(w, model.BadRequest(err), nil)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
zap.S().Debugf("Got PAT request: %+v", req)
|
// 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
|
var apierr basemodel.BaseApiError
|
||||||
if req, apierr = ah.AppDao().CreatePAT(ctx, req); apierr != nil {
|
if pat, apierr = ah.AppDao().CreatePAT(ctx, pat); apierr != nil {
|
||||||
RespondError(w, apierr, nil)
|
RespondError(w, apierr, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ah.Respond(w, &req)
|
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()
|
||||||
|
|
||||||
|
req := model.PAT{}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
RespondError(w, model.BadRequest(err), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := auth.GetUserFromRequest(r)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, &model.ApiError{
|
||||||
|
Typ: model.ErrorUnauthorized,
|
||||||
|
Err: err,
|
||||||
|
}, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = validatePATRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, model.BadRequest(err), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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 apierr = ah.AppDao().UpdatePAT(ctx, req, id); apierr != nil {
|
||||||
|
RespondError(w, apierr, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ah.Respond(w, map[string]string{"data": "pat updated successfully"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) getPATs(w http.ResponseWriter, r *http.Request) {
|
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)
|
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()
|
ctx := context.Background()
|
||||||
id := mux.Vars(r)["id"]
|
id := mux.Vars(r)["id"]
|
||||||
user, err := auth.GetUserFromRequest(r)
|
user, err := auth.GetUserFromRequest(r)
|
||||||
@ -105,14 +163,14 @@ func (ah *APIHandler) deletePAT(w http.ResponseWriter, r *http.Request) {
|
|||||||
if pat.UserID != user.Id {
|
if pat.UserID != user.Id {
|
||||||
RespondError(w, &model.ApiError{
|
RespondError(w, &model.ApiError{
|
||||||
Typ: model.ErrorUnauthorized,
|
Typ: model.ErrorUnauthorized,
|
||||||
Err: fmt.Errorf("unauthorized PAT delete request"),
|
Err: fmt.Errorf("unauthorized PAT revoke request"),
|
||||||
}, nil)
|
}, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
zap.S().Debugf("Delete PAT with id: %+v", id)
|
zap.S().Debugf("Revoke PAT with id: %+v", id)
|
||||||
if apierr := ah.AppDao().DeletePAT(ctx, id); apierr != nil {
|
if apierr := ah.AppDao().RevokePAT(ctx, id, user.Id); apierr != nil {
|
||||||
RespondError(w, apierr, nil)
|
RespondError(w, apierr, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ah.Respond(w, map[string]string{"data": "pat deleted successfully"})
|
ah.Respond(w, map[string]string{"data": "pat revoked successfully"})
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,11 @@ import (
|
|||||||
"github.com/soheilhy/cmux"
|
"github.com/soheilhy/cmux"
|
||||||
"go.signoz.io/signoz/ee/query-service/app/api"
|
"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/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/constants"
|
||||||
"go.signoz.io/signoz/ee/query-service/dao"
|
"go.signoz.io/signoz/ee/query-service/dao"
|
||||||
"go.signoz.io/signoz/ee/query-service/interfaces"
|
"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"
|
baseInterface "go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
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/logparsingpipeline"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/opamp"
|
"go.signoz.io/signoz/pkg/query-service/app/opamp"
|
||||||
opAmpModel "go.signoz.io/signoz/pkg/query-service/app/opamp/model"
|
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"
|
"go.signoz.io/signoz/pkg/query-service/cache"
|
||||||
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
"go.signoz.io/signoz/pkg/query-service/healthcheck"
|
"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()
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
// add auth middleware
|
||||||
getUserFromRequest := func(r *http.Request) (*basemodel.UserPayload, error) {
|
getUserFromRequest := func(r *http.Request) (*basemodel.UserPayload, error) {
|
||||||
patToken := r.Header.Get("SIGNOZ-API-KEY")
|
return auth.GetUserFromRequest(r, apiHandler)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
am := baseapp.NewAuthMiddleware(getUserFromRequest)
|
am := baseapp.NewAuthMiddleware(getUserFromRequest)
|
||||||
|
|
||||||
r.Use(setTimeoutMiddleware)
|
r.Use(setTimeoutMiddleware)
|
||||||
r.Use(s.analyticsMiddleware)
|
r.Use(s.analyticsMiddleware)
|
||||||
r.Use(loggingMiddleware)
|
r.Use(loggingMiddleware)
|
||||||
@ -439,7 +426,7 @@ func extractQueryRangeV3Data(path string, r *http.Request) (map[string]interface
|
|||||||
telemetry.GetInstance().AddActiveLogsUser()
|
telemetry.GetInstance().AddActiveLogsUser()
|
||||||
}
|
}
|
||||||
data["dataSources"] = dataSources
|
data["dataSources"] = dataSources
|
||||||
userEmail, err := auth.GetEmailFromJwt(r.Context())
|
userEmail, err := baseauth.GetEmailFromJwt(r.Context())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_QUERY_RANGE_V3, data, userEmail, true)
|
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 {
|
func (s *Server) analyticsMiddleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
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)
|
r = r.WithContext(ctx)
|
||||||
route := mux.CurrentRoute(r)
|
route := mux.CurrentRoute(r)
|
||||||
path, _ := route.GetPathTemplate()
|
path, _ := route.GetPathTemplate()
|
||||||
@ -482,7 +469,7 @@ func (s *Server) analyticsMiddleware(next http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := telemetry.EnabledPaths()[path]; ok {
|
if _, ok := telemetry.EnabledPaths()[path]; ok {
|
||||||
userEmail, err := auth.GetEmailFromJwt(r.Context())
|
userEmail, err := baseauth.GetEmailFromJwt(r.Context())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_PATH, data, userEmail)
|
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_PATH, data, userEmail)
|
||||||
}
|
}
|
||||||
|
56
ee/query-service/auth/auth.go
Normal file
56
ee/query-service/auth/auth.go
Normal file
@ -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)
|
||||||
|
}
|
@ -34,9 +34,11 @@ type ModelDao interface {
|
|||||||
GetDomainByEmail(ctx context.Context, email string) (*model.OrgDomain, basemodel.BaseApiError)
|
GetDomainByEmail(ctx context.Context, email string) (*model.OrgDomain, basemodel.BaseApiError)
|
||||||
|
|
||||||
CreatePAT(ctx context.Context, p model.PAT) (model.PAT, 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)
|
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)
|
GetPATByID(ctx context.Context, id string) (*model.PAT, basemodel.BaseApiError)
|
||||||
GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError)
|
GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError)
|
||||||
ListPATs(ctx context.Context, userID string) ([]model.PAT, 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
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
basedao "go.signoz.io/signoz/pkg/query-service/dao"
|
basedao "go.signoz.io/signoz/pkg/query-service/dao"
|
||||||
basedsql "go.signoz.io/signoz/pkg/query-service/dao/sqlite"
|
basedsql "go.signoz.io/signoz/pkg/query-service/dao/sqlite"
|
||||||
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
|
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type modelDao struct {
|
type modelDao struct {
|
||||||
@ -28,6 +29,41 @@ func (m *modelDao) checkFeature(key string) error {
|
|||||||
return m.flags.CheckFeature(key)
|
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
|
// InitDB creates and extends base model DB repository
|
||||||
func InitDB(dataSourceName string) (*modelDao, error) {
|
func InitDB(dataSourceName string) (*modelDao, error) {
|
||||||
dao, err := basedsql.InitDB(dataSourceName)
|
dao, err := basedsql.InitDB(dataSourceName)
|
||||||
@ -51,11 +87,16 @@ func InitDB(dataSourceName string) (*modelDao, error) {
|
|||||||
);
|
);
|
||||||
CREATE TABLE IF NOT EXISTS personal_access_tokens (
|
CREATE TABLE IF NOT EXISTS personal_access_tokens (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
role TEXT NOT NULL,
|
||||||
user_id TEXT NOT NULL,
|
user_id TEXT NOT NULL,
|
||||||
token TEXT NOT NULL UNIQUE,
|
token TEXT NOT NULL UNIQUE,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
created_at INTEGER NOT NULL,
|
created_at INTEGER NOT NULL,
|
||||||
expires_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)
|
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())
|
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
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"go.signoz.io/signoz/ee/query-service/model"
|
"go.signoz.io/signoz/ee/query-service/model"
|
||||||
basemodel "go.signoz.io/signoz/pkg/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) {
|
func (m *modelDao) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basemodel.BaseApiError) {
|
||||||
result, err := m.DB().ExecContext(ctx,
|
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.UserID,
|
||||||
p.Token,
|
p.Token,
|
||||||
|
p.Role,
|
||||||
p.Name,
|
p.Name,
|
||||||
p.CreatedAt,
|
p.CreatedAt,
|
||||||
p.ExpiresAt)
|
p.ExpiresAt,
|
||||||
|
p.UpdatedAt,
|
||||||
|
p.UpdatedByUserID,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.S().Errorf("Failed to insert PAT in db, err: %v", zap.Error(err))
|
zap.S().Errorf("Failed to insert PAT in db, err: %v", zap.Error(err))
|
||||||
return model.PAT{}, model.InternalError(fmt.Errorf("PAT insertion failed"))
|
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"))
|
return model.PAT{}, model.InternalError(fmt.Errorf("PAT insertion failed"))
|
||||||
}
|
}
|
||||||
p.Id = strconv.Itoa(int(id))
|
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
|
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) {
|
func (m *modelDao) ListPATs(ctx context.Context, userID string) ([]model.PAT, basemodel.BaseApiError) {
|
||||||
pats := []model.PAT{}
|
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))
|
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"))
|
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
|
return pats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *modelDao) DeletePAT(ctx context.Context, id string) basemodel.BaseApiError {
|
func (m *modelDao) RevokePAT(ctx context.Context, id string, userID string) basemodel.BaseApiError {
|
||||||
_, err := m.DB().ExecContext(ctx, `DELETE from personal_access_tokens where id=?;`, id)
|
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 {
|
if err != nil {
|
||||||
zap.S().Errorf("Failed to delete PAT, err: %v", zap.Error(err))
|
zap.S().Errorf("Failed to revoke PAT in db, err: %v", zap.Error(err))
|
||||||
return model.InternalError(fmt.Errorf("failed to delete PAT"))
|
return model.InternalError(fmt.Errorf("PAT revoke failed"))
|
||||||
}
|
}
|
||||||
return nil
|
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) {
|
func (m *modelDao) GetPAT(ctx context.Context, token string) (*model.PAT, basemodel.BaseApiError) {
|
||||||
pats := []model.PAT{}
|
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"))
|
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) {
|
func (m *modelDao) GetPATByID(ctx context.Context, id string) (*model.PAT, basemodel.BaseApiError) {
|
||||||
pats := []model.PAT{}
|
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"))
|
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
|
return &pats[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deprecated
|
||||||
func (m *modelDao) GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError) {
|
func (m *modelDao) GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError) {
|
||||||
users := []basemodel.UserPayload{}
|
users := []basemodel.UserPayload{}
|
||||||
|
|
||||||
|
@ -1,10 +1,32 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
type PAT struct {
|
type User struct {
|
||||||
Id string `json:"id" db:"id"`
|
Id string `json:"id" db:"id"`
|
||||||
UserID string `json:"userId" db:"user_id"`
|
Name string `json:"name" db:"name"`
|
||||||
Token string `json:"token" db:"token"`
|
Email string `json:"email" db:"email"`
|
||||||
Name string `json:"name" db:"name"`
|
CreatedAt int64 `json:"createdAt" db:"created_at"`
|
||||||
CreatedAt int64 `json:"createdAt" db:"created_at"`
|
ProfilePictureURL string `json:"profilePictureURL" db:"profile_picture_url"`
|
||||||
ExpiresAt int64 `json:"expiresAt" db:"expires_at"`
|
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"`
|
||||||
|
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"`
|
||||||
}
|
}
|
||||||
|
@ -152,6 +152,7 @@ type Telemetry struct {
|
|||||||
maxRandInt int
|
maxRandInt int
|
||||||
rateLimits map[string]int8
|
rateLimits map[string]int8
|
||||||
activeUser map[string]int8
|
activeUser map[string]int8
|
||||||
|
patTokenUser bool
|
||||||
countUsers int8
|
countUsers int8
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
@ -243,7 +244,9 @@ func createTelemetry() {
|
|||||||
"metricsTTLStatus": metricsTTL.Status,
|
"metricsTTLStatus": metricsTTL.Status,
|
||||||
"tracesTTLStatus": traceTTL.Status,
|
"tracesTTLStatus": traceTTL.Status,
|
||||||
"logsTTLStatus": logsTTL.Status,
|
"logsTTLStatus": logsTTL.Status,
|
||||||
|
"patUser": telemetry.patTokenUser,
|
||||||
}
|
}
|
||||||
|
telemetry.patTokenUser = false
|
||||||
for key, value := range tsInfo {
|
for key, value := range tsInfo {
|
||||||
data[key] = value
|
data[key] = value
|
||||||
}
|
}
|
||||||
@ -346,6 +349,10 @@ func (a *Telemetry) SetUserEmail(email string) {
|
|||||||
a.userEmail = email
|
a.userEmail = email
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Telemetry) SetPatTokenUser() {
|
||||||
|
a.patTokenUser = true
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Telemetry) GetUserEmail() string {
|
func (a *Telemetry) GetUserEmail() string {
|
||||||
return a.userEmail
|
return a.userEmail
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user