signoz/pkg/types/user.go
Nityananda Gohain 0a2b7ca1d8
chore(auth): refactor the auth modules and handler in preparation for multi tenant login (#7778)
* chore: update auth

* chore: password changes

* chore: make changes in oss code

* chore: login

* chore: get to a running state

* fix: migration inital commit

* fix: signoz cloud intgtn tests

* fix: minor fixes

* chore: sso code fixed with org domain

* fix: tests

* fix: ee auth api's

* fix: changes in name

* fix: return user in login api

* fix: address comments

* fix: validate password

* fix: handle get domain by email properly

* fix: move authomain to usermodule

* fix: use displayname instead of hname

* fix: rename back endpoints

* fix: update telemetry

* fix: correct errors

* fix: test and fix the invite endpoints

* fix: delete all things related to user in store

* fix: address issues

* fix: ee delete invite

* fix: rename func

* fix: update user and update role

* fix: update role

* fix: login and invite changes

* fix: return org name in users response

* fix: update user role

* fix: nil check

* fix: getinvite and update role

* fix: sso

* fix: getinvite use sso ctx

* fix: use correct sourceurl

* fix: getsourceurl from req payload

* fix: update created_at

* fix: fix reset password

* fix: sso signup and token password change

* fix: don't delete last admin

* fix: reset password and migration

* fix: migration

* fix: reset password for sso users

* fix: clean up invite

* fix: migration

* fix: update claims and store code

* fix: use correct error

* fix: proper nil checks

* fix: make migration multitenant

* fix: address comments

* fix: minor fixes

* fix: test

* fix: rename reset password

---------

Co-authored-by: Vikrant Gupta <vikrant@signoz.io>
2025-05-14 23:12:55 +05:30

241 lines
7.7 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")
)
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)
}
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,references:org(id),on_delete:CASCADE" 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,references:factor_password(id)" 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"`
}