mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-07-28 23:32:02 +08:00

* chore: update API key * fix: delete api key on user delete * fix: migration * fix: api * fix: address comments * fix: address comments * fix: update structs * fix: minor changes * fix: error message * fix: address comments * fix: integration tests * fix: minor issues * fix: integration tests
250 lines
8.2 KiB
Go
250 lines
8.2 KiB
Go
package types
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/SigNoz/signoz/pkg/errors"
|
|
"github.com/SigNoz/signoz/pkg/valuer"
|
|
"github.com/uptrace/bun"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
const (
|
|
SSOAvailable = "sso_available"
|
|
)
|
|
|
|
var (
|
|
ErrUserAlreadyExists = errors.MustNewCode("user_already_exists")
|
|
ErrPasswordAlreadyExists = errors.MustNewCode("password_already_exists")
|
|
ErrUserNotFound = errors.MustNewCode("user_not_found")
|
|
ErrResetPasswordTokenAlreadyExists = errors.MustNewCode("reset_password_token_already_exists")
|
|
ErrPasswordNotFound = errors.MustNewCode("password_not_found")
|
|
ErrResetPasswordTokenNotFound = errors.MustNewCode("reset_password_token_not_found")
|
|
ErrAPIKeyAlreadyExists = errors.MustNewCode("api_key_already_exists")
|
|
ErrAPIKeyNotFound = errors.MustNewCode("api_key_not_found")
|
|
)
|
|
|
|
type UserStore interface {
|
|
// invite
|
|
CreateBulkInvite(ctx context.Context, invites []*Invite) error
|
|
ListInvite(ctx context.Context, orgID string) ([]*Invite, error)
|
|
DeleteInvite(ctx context.Context, orgID string, id valuer.UUID) error
|
|
GetInviteByToken(ctx context.Context, token string) (*GettableInvite, error)
|
|
GetInviteByEmailInOrg(ctx context.Context, orgID string, email string) (*Invite, error)
|
|
|
|
// user
|
|
CreateUserWithPassword(ctx context.Context, user *User, password *FactorPassword) (*User, error)
|
|
CreateUser(ctx context.Context, user *User) error
|
|
GetUserByID(ctx context.Context, orgID string, id string) (*GettableUser, error)
|
|
GetUserByEmailInOrg(ctx context.Context, orgID string, email string) (*GettableUser, error)
|
|
GetUsersByEmail(ctx context.Context, email string) ([]*GettableUser, error)
|
|
GetUsersByRoleInOrg(ctx context.Context, orgID string, role Role) ([]*GettableUser, error)
|
|
ListUsers(ctx context.Context, orgID string) ([]*GettableUser, error)
|
|
UpdateUser(ctx context.Context, orgID string, id string, user *User) (*User, error)
|
|
DeleteUser(ctx context.Context, orgID string, id string) error
|
|
|
|
// password
|
|
CreatePassword(ctx context.Context, password *FactorPassword) (*FactorPassword, error)
|
|
CreateResetPasswordToken(ctx context.Context, resetPasswordRequest *ResetPasswordRequest) error
|
|
GetPasswordByID(ctx context.Context, id string) (*FactorPassword, error)
|
|
GetPasswordByUserID(ctx context.Context, id string) (*FactorPassword, error)
|
|
GetResetPassword(ctx context.Context, token string) (*ResetPasswordRequest, error)
|
|
GetResetPasswordByPasswordID(ctx context.Context, passwordID string) (*ResetPasswordRequest, error)
|
|
UpdatePassword(ctx context.Context, userID string, password string) error
|
|
UpdatePasswordAndDeleteResetPasswordEntry(ctx context.Context, userID string, password string) error
|
|
|
|
// Auth Domain
|
|
GetDomainByName(ctx context.Context, name string) (*StorableOrgDomain, error)
|
|
|
|
// Temporary func for SSO
|
|
GetDefaultOrgID(ctx context.Context) (string, error)
|
|
|
|
// API KEY
|
|
CreateAPIKey(ctx context.Context, apiKey *StorableAPIKey) error
|
|
UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *StorableAPIKey, updaterID valuer.UUID) error
|
|
ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*StorableAPIKeyUser, error)
|
|
RevokeAPIKey(ctx context.Context, id valuer.UUID, revokedByUserID valuer.UUID) error
|
|
GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*StorableAPIKeyUser, error)
|
|
}
|
|
|
|
type GettableUser struct {
|
|
User
|
|
Organization string `json:"organization"`
|
|
}
|
|
|
|
type User struct {
|
|
bun.BaseModel `bun:"table:users"`
|
|
|
|
Identifiable
|
|
TimeAuditable
|
|
DisplayName string `bun:"display_name,type:text,notnull" json:"displayName"`
|
|
Email string `bun:"email,type:text,notnull,unique:org_email" json:"email"`
|
|
Role string `bun:"role,type:text,notnull" json:"role"`
|
|
OrgID string `bun:"org_id,type:text,notnull,unique:org_email" json:"orgId"`
|
|
}
|
|
|
|
func NewUser(displayName string, email string, role string, orgID string) (*User, error) {
|
|
if email == "" {
|
|
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "email is required")
|
|
}
|
|
|
|
if role == "" {
|
|
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "role is required")
|
|
}
|
|
|
|
if orgID == "" {
|
|
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgID is required")
|
|
}
|
|
|
|
return &User{
|
|
Identifiable: Identifiable{
|
|
ID: valuer.GenerateUUID(),
|
|
},
|
|
TimeAuditable: TimeAuditable{
|
|
CreatedAt: time.Now(),
|
|
},
|
|
DisplayName: displayName,
|
|
Email: email,
|
|
Role: role,
|
|
OrgID: orgID,
|
|
}, nil
|
|
}
|
|
|
|
type PostableRegisterOrgAndAdmin struct {
|
|
PostableAcceptInvite
|
|
Name string `json:"name"`
|
|
OrgID string `json:"orgId"`
|
|
OrgDisplayName string `json:"orgDisplayName"`
|
|
OrgName string `json:"orgName"`
|
|
Email string `json:"email"`
|
|
}
|
|
|
|
type PostableAcceptInvite struct {
|
|
DisplayName string `json:"displayName"`
|
|
InviteToken string `json:"token"`
|
|
Password string `json:"password"`
|
|
|
|
// reference URL to track where the register request is coming from
|
|
SourceURL string `json:"sourceUrl"`
|
|
}
|
|
|
|
func (p *PostableAcceptInvite) Validate() error {
|
|
if p.InviteToken == "" {
|
|
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "invite token is required")
|
|
}
|
|
|
|
if p.Password == "" || len(p.Password) < 8 {
|
|
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "password must be at least 8 characters long")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type FactorPassword struct {
|
|
bun.BaseModel `bun:"table:factor_password"`
|
|
|
|
Identifiable
|
|
TimeAuditable
|
|
Password string `bun:"password,type:text,notnull" json:"password"`
|
|
Temporary bool `bun:"temporary,type:boolean,notnull" json:"temporary"`
|
|
UserID string `bun:"user_id,type:text,notnull,unique,references:user(id)" json:"userId"`
|
|
}
|
|
|
|
func NewFactorPassword(password string) (*FactorPassword, error) {
|
|
|
|
if password == "" && len(password) < 8 {
|
|
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "password must be at least 8 characters long")
|
|
}
|
|
|
|
password = strings.TrimSpace(password)
|
|
|
|
hashedPassword, err := HashPassword(password)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &FactorPassword{
|
|
Identifiable: Identifiable{
|
|
ID: valuer.GenerateUUID(),
|
|
},
|
|
TimeAuditable: TimeAuditable{
|
|
CreatedAt: time.Now(),
|
|
},
|
|
Password: hashedPassword,
|
|
Temporary: false,
|
|
}, nil
|
|
}
|
|
|
|
func HashPassword(password string) (string, error) {
|
|
// bcrypt automatically handles salting and uses a secure work factor
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(hashedPassword), nil
|
|
}
|
|
|
|
func ComparePassword(hashedPassword, password string) bool {
|
|
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) == nil
|
|
}
|
|
|
|
type ResetPasswordRequest struct {
|
|
bun.BaseModel `bun:"table:reset_password_token"`
|
|
|
|
Identifiable
|
|
Token string `bun:"token,type:text,notnull" json:"token"`
|
|
PasswordID string `bun:"password_id,type:text,notnull,unique" json:"passwordId"`
|
|
}
|
|
|
|
func NewResetPasswordRequest(passwordID string) (*ResetPasswordRequest, error) {
|
|
return &ResetPasswordRequest{
|
|
Identifiable: Identifiable{
|
|
ID: valuer.GenerateUUID(),
|
|
},
|
|
Token: valuer.GenerateUUID().String(),
|
|
PasswordID: passwordID,
|
|
}, nil
|
|
}
|
|
|
|
type PostableResetPassword struct {
|
|
Password string `json:"password"`
|
|
Token string `json:"token"`
|
|
}
|
|
|
|
type ChangePasswordRequest struct {
|
|
UserId string `json:"userId"`
|
|
OldPassword string `json:"oldPassword"`
|
|
NewPassword string `json:"newPassword"`
|
|
}
|
|
|
|
type PostableLoginRequest struct {
|
|
OrgID string `json:"orgId"`
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
RefreshToken string `json:"refreshToken"`
|
|
}
|
|
|
|
type GettableUserJwt struct {
|
|
AccessJwt string `json:"accessJwt"`
|
|
AccessJwtExpiry int64 `json:"accessJwtExpiry"`
|
|
RefreshJwt string `json:"refreshJwt"`
|
|
RefreshJwtExpiry int64 `json:"refreshJwtExpiry"`
|
|
}
|
|
|
|
type GettableLoginResponse struct {
|
|
GettableUserJwt
|
|
UserID string `json:"userId"`
|
|
}
|
|
|
|
type GettableLoginPrecheck struct {
|
|
SSO bool `json:"sso"`
|
|
SSOUrl string `json:"ssoUrl"`
|
|
CanSelfRegister bool `json:"canSelfRegister"`
|
|
IsUser bool `json:"isUser"`
|
|
SSOError string `json:"ssoError"`
|
|
SelectOrg bool `json:"selectOrg"`
|
|
Orgs []string `json:"orgs"`
|
|
}
|