2022-05-03 15:26:32 +05:30
package auth
import (
2023-12-21 18:27:30 +05:30
"bytes"
2022-05-03 15:26:32 +05:30
"context"
"fmt"
2023-12-21 18:27:30 +05:30
"os"
"text/template"
2022-05-03 15:26:32 +05:30
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
2022-10-06 20:13:30 +05:30
2025-03-20 21:01:41 +05:30
"github.com/SigNoz/signoz/pkg/alertmanager"
2025-04-25 19:38:15 +05:30
"github.com/SigNoz/signoz/pkg/modules/organization"
2025-03-20 21:01:41 +05:30
"github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/query-service/dao"
"github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/SigNoz/signoz/pkg/query-service/telemetry"
"github.com/SigNoz/signoz/pkg/query-service/utils"
smtpservice "github.com/SigNoz/signoz/pkg/query-service/utils/smtpService"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
2025-03-25 22:02:34 +05:30
"github.com/SigNoz/signoz/pkg/valuer"
2022-05-03 15:26:32 +05:30
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
)
const (
opaqueTokenSize = 16
minimumPasswordLength = 8
)
var (
2025-04-26 15:50:02 +05:30
ErrorInvalidCreds = fmt . Errorf ( "invalid credentials" )
ErrorEmptyRequest = errors . New ( "Empty request" )
ErrorInvalidRole = errors . New ( "Invalid role" )
ErrorInvalidInviteToken = errors . New ( "Invalid invite token" )
ErrorAskAdmin = errors . New ( "An invitation is needed to create an account. Please ask your admin (the person who has first installed SIgNoz) to send an invite." )
2022-05-03 15:26:32 +05:30
)
2023-12-21 18:27:30 +05:30
type InviteEmailData struct {
CustomerName string
InviterName string
InviterEmail string
Link string
}
2022-05-03 15:26:32 +05:30
// 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 ) {
2025-04-26 15:50:02 +05:30
claims , err := authtypes . ClaimsFromContext ( ctx )
if err != nil {
return nil , err
}
2022-05-03 15:26:32 +05:30
2023-10-29 16:58:31 +05:30
token , err := utils . RandomHex ( opaqueTokenSize )
2022-05-03 15:26:32 +05:30
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" )
}
2024-10-17 18:41:31 +05:30
// Check if an invite already exists
invite , apiErr := dao . DB ( ) . GetInviteFromEmail ( ctx , req . Email )
if apiErr != nil {
return nil , errors . Wrap ( apiErr . Err , "Failed to check existing invite" )
}
if invite != nil {
return nil , errors . New ( "An invite already exists for this email" )
}
2025-04-26 15:50:02 +05:30
role , err := authtypes . NewRole ( req . Role )
if err != nil {
return nil , err
2022-05-03 15:26:32 +05:30
}
2025-02-17 18:16:41 +05:30
au , apiErr := dao . DB ( ) . GetUser ( ctx , claims . UserID )
2022-05-03 15:26:32 +05:30
if apiErr != nil {
return nil , errors . Wrap ( err , "failed to query admin user from the DB" )
}
2024-10-17 18:41:31 +05:30
2025-03-06 15:39:45 +05:30
inv := & types . Invite {
2025-03-25 22:02:34 +05:30
Identifiable : types . Identifiable {
ID : valuer . GenerateUUID ( ) ,
} ,
TimeAuditable : types . TimeAuditable {
CreatedAt : time . Now ( ) ,
UpdatedAt : time . Now ( ) ,
} ,
Name : req . Name ,
Email : req . Email ,
Token : token ,
2025-04-26 15:50:02 +05:30
Role : role . String ( ) ,
2025-03-25 22:02:34 +05:30
OrgID : au . OrgID ,
2024-10-17 18:41:31 +05:30
}
if err := dao . DB ( ) . CreateInviteEntry ( ctx , inv ) ; err != nil {
return nil , errors . Wrap ( err . Err , "failed to write to DB" )
}
telemetry . GetInstance ( ) . SendEvent ( telemetry . TELEMETRY_EVENT_USER_INVITATION_SENT , map [ string ] interface { } {
"invited user email" : req . Email ,
} , au . Email , true , false )
// send email if SMTP is enabled
if os . Getenv ( "SMTP_ENABLED" ) == "true" && req . FrontendBaseUrl != "" {
inviteEmail ( req , au , token )
}
return & model . InviteResponse { Email : inv . Email , InviteToken : inv . Token } , nil
}
func InviteUsers ( ctx context . Context , req * model . BulkInviteRequest ) ( * model . BulkInviteResponse , error ) {
2025-04-26 15:50:02 +05:30
claims , err := authtypes . ClaimsFromContext ( ctx )
if err != nil {
return nil , err
}
2024-10-17 18:41:31 +05:30
response := & model . BulkInviteResponse {
Status : "success" ,
Summary : model . InviteSummary { TotalInvites : len ( req . Users ) } ,
SuccessfulInvites : [ ] model . SuccessfulInvite { } ,
FailedInvites : [ ] model . FailedInvite { } ,
}
2025-02-17 18:16:41 +05:30
au , apiErr := dao . DB ( ) . GetUser ( ctx , claims . UserID )
2024-10-17 18:41:31 +05:30
if apiErr != nil {
return nil , errors . Wrap ( apiErr . Err , "failed to query admin user from the DB" )
}
for _ , inviteReq := range req . Users {
inviteResp , err := inviteUser ( ctx , & inviteReq , au )
if err != nil {
response . FailedInvites = append ( response . FailedInvites , model . FailedInvite {
Email : inviteReq . Email ,
Error : err . Error ( ) ,
} )
response . Summary . FailedInvites ++
} else {
response . SuccessfulInvites = append ( response . SuccessfulInvites , model . SuccessfulInvite {
Email : inviteResp . Email ,
InviteLink : fmt . Sprintf ( "%s/signup?token=%s" , inviteReq . FrontendBaseUrl , inviteResp . InviteToken ) ,
Status : "sent" ,
} )
response . Summary . SuccessfulInvites ++
}
}
// Update the status based on the results
if response . Summary . FailedInvites == response . Summary . TotalInvites {
response . Status = "failure"
} else if response . Summary . FailedInvites > 0 {
response . Status = "partial_success"
}
return response , nil
}
// Helper function to handle individual invites
2025-03-06 15:39:45 +05:30
func inviteUser ( ctx context . Context , req * model . InviteRequest , au * types . GettableUser ) ( * model . InviteResponse , error ) {
2024-10-17 18:41:31 +05:30
token , err := utils . 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" )
}
// Check if an invite already exists
invite , apiErr := dao . DB ( ) . GetInviteFromEmail ( ctx , req . Email )
if apiErr != nil {
return nil , errors . Wrap ( apiErr . Err , "Failed to check existing invite" )
}
if invite != nil {
return nil , errors . New ( "An invite already exists for this email" )
}
2025-04-26 15:50:02 +05:30
role , err := authtypes . NewRole ( req . Role )
if err != nil {
return nil , err
2024-10-17 18:41:31 +05:30
}
2025-03-06 15:39:45 +05:30
inv := & types . Invite {
2025-03-25 22:02:34 +05:30
Identifiable : types . Identifiable {
ID : valuer . GenerateUUID ( ) ,
} ,
TimeAuditable : types . TimeAuditable {
CreatedAt : time . Now ( ) ,
UpdatedAt : time . Now ( ) ,
} ,
Name : req . Name ,
Email : req . Email ,
Token : token ,
2025-04-26 15:50:02 +05:30
Role : role . String ( ) ,
2025-03-25 22:02:34 +05:30
OrgID : au . OrgID ,
2022-05-03 15:26:32 +05:30
}
if err := dao . DB ( ) . CreateInviteEntry ( ctx , inv ) ; err != nil {
return nil , errors . Wrap ( err . Err , "failed to write to DB" )
}
2023-11-17 16:18:31 +05:30
telemetry . GetInstance ( ) . SendEvent ( telemetry . TELEMETRY_EVENT_USER_INVITATION_SENT , map [ string ] interface { } {
"invited user email" : req . Email ,
2024-03-28 21:43:41 +05:30
} , au . Email , true , false )
2023-11-17 16:18:31 +05:30
2023-12-21 18:27:30 +05:30
// send email if SMTP is enabled
if os . Getenv ( "SMTP_ENABLED" ) == "true" && req . FrontendBaseUrl != "" {
inviteEmail ( req , au , token )
}
2022-05-03 15:26:32 +05:30
return & model . InviteResponse { Email : inv . Email , InviteToken : inv . Token } , nil
}
2025-03-06 15:39:45 +05:30
func inviteEmail ( req * model . InviteRequest , au * types . GettableUser , token string ) {
2023-12-21 18:27:30 +05:30
smtp := smtpservice . GetInstance ( )
data := InviteEmailData {
CustomerName : req . Name ,
InviterName : au . Name ,
InviterEmail : au . Email ,
Link : fmt . Sprintf ( "%s/signup?token=%s" , req . FrontendBaseUrl , token ) ,
}
tmpl , err := template . ParseFiles ( constants . InviteEmailTemplate )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "failed to send email" , zap . Error ( err ) )
2023-12-21 18:27:30 +05:30
return
}
var body bytes . Buffer
if err := tmpl . Execute ( & body , data ) ; err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "failed to send email" , zap . Error ( err ) )
2023-12-21 18:27:30 +05:30
return
}
err = smtp . SendEmail (
req . Email ,
au . Name + " has invited you to their team in SigNoz" ,
body . String ( ) ,
)
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "failed to send email" , zap . Error ( err ) )
2023-12-21 18:27:30 +05:30
return
}
}
2022-05-03 15:26:32 +05:30
// RevokeInvite is used to revoke the invitation for the given email.
func RevokeInvite ( ctx context . Context , email string ) error {
2025-04-26 15:50:02 +05:30
claims , err := authtypes . ClaimsFromContext ( ctx )
if err != nil {
return err
2025-03-06 15:39:45 +05:30
}
if err := dao . DB ( ) . DeleteInvitation ( ctx , claims . OrgID , email ) ; err != nil {
2022-05-03 15:26:32 +05:30
return errors . Wrap ( err . Err , "failed to write to DB" )
}
return nil
}
// GetInvite returns an invitation object for the given token.
2025-04-25 19:38:15 +05:30
func GetInvite ( ctx context . Context , token string , organizationModule organization . Module ) ( * model . InvitationResponseObject , error ) {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Debug ( "GetInvite method invoked for token" , zap . String ( "token" , token ) )
2022-05-03 15:26:32 +05:30
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" )
}
2025-04-25 19:38:15 +05:30
orgID , err := valuer . NewUUID ( inv . OrgID )
if err != nil {
return nil , err
}
org , err := organizationModule . Get ( ctx , orgID )
if err != nil {
return nil , errors . Wrap ( err , "failed to query the DB" )
2022-05-03 15:26:32 +05:30
}
return & model . InvitationResponseObject {
Name : inv . Name ,
Email : inv . Email ,
Token : inv . Token ,
2025-03-06 15:39:45 +05:30
CreatedAt : inv . CreatedAt . Unix ( ) ,
2022-05-03 15:26:32 +05:30
Role : inv . Role ,
2025-04-25 19:38:15 +05:30
Organization : org . DisplayName ,
2022-05-03 15:26:32 +05:30
} , nil
}
2025-03-06 15:39:45 +05:30
func ValidateInvite ( ctx context . Context , req * RegisterRequest ) ( * types . Invite , error ) {
2022-05-03 15:26:32 +05:30
invitation , err := dao . DB ( ) . GetInviteFromEmail ( ctx , req . Email )
if err != nil {
return nil , errors . Wrap ( err . Err , "Failed to read from DB" )
}
2022-05-04 17:33:54 +05:30
if invitation == nil {
return nil , ErrorAskAdmin
}
if invitation . Token != req . InviteToken {
2022-05-03 15:26:32 +05:30
return nil , ErrorInvalidInviteToken
}
return invitation , nil
}
2025-03-06 15:39:45 +05:30
func CreateResetPasswordToken ( ctx context . Context , userId string ) ( * types . ResetPasswordRequest , error ) {
2023-10-29 16:58:31 +05:30
token , err := utils . RandomHex ( opaqueTokenSize )
2022-05-03 15:26:32 +05:30
if err != nil {
return nil , errors . Wrap ( err , "failed to generate reset password token" )
}
2025-03-06 15:39:45 +05:30
req := & types . ResetPasswordRequest {
2025-04-04 01:46:28 +05:30
Identifiable : types . Identifiable {
ID : valuer . GenerateUUID ( ) ,
} ,
2025-03-06 15:39:45 +05:30
UserID : userId ,
2022-05-03 15:26:32 +05:30
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" )
}
2023-08-23 16:22:24 +05:30
hash , err := PasswordHash ( req . Password )
2022-05-03 15:26:32 +05:30
if err != nil {
return errors . Wrap ( err , "Failed to generate password hash" )
}
2025-03-06 15:39:45 +05:30
if apiErr := dao . DB ( ) . UpdateUserPassword ( ctx , hash , entry . UserID ) ; apiErr != nil {
2022-05-03 15:26:32 +05:30
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
}
2024-03-26 06:20:35 +05:30
func ChangePassword ( ctx context . Context , req * model . ChangePasswordRequest ) * model . ApiError {
2022-05-03 15:26:32 +05:30
user , apiErr := dao . DB ( ) . GetUser ( ctx , req . UserId )
if apiErr != nil {
2024-03-26 06:20:35 +05:30
return apiErr
2022-05-03 15:26:32 +05:30
}
if user == nil || ! passwordMatch ( user . Password , req . OldPassword ) {
2024-03-26 06:20:35 +05:30
return model . ForbiddenError ( ErrorInvalidCreds )
2022-05-03 15:26:32 +05:30
}
2023-08-23 16:22:24 +05:30
hash , err := PasswordHash ( req . NewPassword )
2022-05-03 15:26:32 +05:30
if err != nil {
2024-03-26 06:20:35 +05:30
return model . InternalError ( errors . New ( "Failed to generate password hash" ) )
2022-05-03 15:26:32 +05:30
}
2025-03-06 15:39:45 +05:30
if apiErr := dao . DB ( ) . UpdateUserPassword ( ctx , hash , user . ID ) ; apiErr != nil {
2024-03-26 06:20:35 +05:30
return apiErr
2022-05-03 15:26:32 +05:30
}
return nil
}
type RegisterRequest struct {
2025-04-25 19:38:15 +05:30
Name string ` json:"name" `
OrgID string ` json:"orgId" `
OrgDisplayName string ` json:"orgDisplayName" `
OrgName string ` json:"orgName" `
Email string ` json:"email" `
Password string ` json:"password" `
InviteToken string ` json:"token" `
2022-10-06 20:13:30 +05:30
// reference URL to track where the register request is coming from
SourceUrl string ` json:"sourceUrl" `
2022-05-03 15:26:32 +05:30
}
2025-04-25 19:38:15 +05:30
func RegisterFirstUser ( ctx context . Context , req * RegisterRequest , organizationModule organization . Module ) ( * types . User , * model . ApiError ) {
2022-10-06 20:13:30 +05:30
if req . Email == "" {
return nil , model . BadRequest ( model . ErrEmailRequired { } )
}
2022-05-03 15:26:32 +05:30
2022-10-06 20:13:30 +05:30
if req . Password == "" {
return nil , model . BadRequest ( model . ErrPasswordRequired { } )
}
2025-04-25 19:38:15 +05:30
organization := types . NewOrganization ( req . OrgDisplayName )
err := organizationModule . Create ( ctx , organization )
if err != nil {
return nil , model . InternalError ( err )
2022-10-06 20:13:30 +05:30
}
var hash string
2023-08-23 16:22:24 +05:30
hash , err = PasswordHash ( req . Password )
2022-10-06 20:13:30 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "failed to generate password hash when registering a user" , zap . Error ( err ) )
2022-10-06 20:13:30 +05:30
return nil , model . InternalError ( model . ErrSignupFailed { } )
2022-05-03 15:26:32 +05:30
}
2025-03-06 15:39:45 +05:30
user := & types . User {
2025-04-15 21:05:36 +05:30
ID : uuid . New ( ) . String ( ) ,
2025-03-06 15:39:45 +05:30
Name : req . Name ,
Email : req . Email ,
Password : hash ,
TimeAuditable : types . TimeAuditable {
CreatedAt : time . Now ( ) ,
} ,
2023-08-12 10:10:25 +05:30
ProfilePictureURL : "" , // Currently unused
2025-04-26 15:50:02 +05:30
Role : authtypes . RoleAdmin . String ( ) ,
2025-04-25 19:38:15 +05:30
OrgID : organization . ID . StringValue ( ) ,
2022-10-06 20:13:30 +05:30
}
2022-05-03 15:26:32 +05:30
2022-11-23 16:57:49 +05:30
return dao . DB ( ) . CreateUser ( ctx , user , true )
2022-10-06 20:13:30 +05:30
}
// RegisterInvitedUser handles registering a invited user
2025-03-06 15:39:45 +05:30
func RegisterInvitedUser ( ctx context . Context , req * RegisterRequest , nopassword bool ) ( * types . User , * model . ApiError ) {
2022-10-06 20:13:30 +05:30
if req . InviteToken == "" {
2023-02-24 14:57:07 +05:30
return nil , model . BadRequest ( ErrorAskAdmin )
2022-05-03 15:26:32 +05:30
}
2022-10-06 20:13:30 +05:30
if ! nopassword && req . Password == "" {
return nil , model . BadRequest ( model . ErrPasswordRequired { } )
}
invite , err := ValidateInvite ( ctx , req )
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "failed to validate invite token" , zap . Error ( err ) )
2022-10-06 20:13:30 +05:30
return nil , model . BadRequest ( model . ErrSignupFailed { } )
}
// checking if user email already exists, this is defensive but
// required as delete invitation and user creation dont happen
// in the same transaction at the end of this function
userPayload , apierr := dao . DB ( ) . GetUserByEmail ( ctx , invite . Email )
if apierr != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "failed to get user by email" , zap . Error ( apierr . Err ) )
2022-10-06 20:13:30 +05:30
return nil , apierr
}
if userPayload != nil {
// user already exists
return & userPayload . User , nil
}
2025-03-06 15:39:45 +05:30
if invite . OrgID == "" {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "failed to find org in the invite" )
2022-10-06 20:13:30 +05:30
return nil , model . InternalError ( fmt . Errorf ( "invalid invite, org not found" ) )
}
if invite . Role == "" {
// if role is not provided, default to viewer
2025-04-26 15:50:02 +05:30
invite . Role = authtypes . RoleViewer . String ( )
2022-05-03 15:26:32 +05:30
}
2022-10-06 20:13:30 +05:30
var hash string
// check if password is not empty, as for SSO case it can be
if req . Password != "" {
2023-08-23 16:22:24 +05:30
hash , err = PasswordHash ( req . Password )
2022-10-06 20:13:30 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "failed to generate password hash when registering a user" , zap . Error ( err ) )
2022-10-06 20:13:30 +05:30
return nil , model . InternalError ( model . ErrSignupFailed { } )
}
} else {
2023-08-23 16:22:24 +05:30
hash , err = PasswordHash ( utils . GeneratePassowrd ( ) )
2022-10-06 20:13:30 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "failed to generate password hash when registering a user" , zap . Error ( err ) )
2022-10-06 20:13:30 +05:30
return nil , model . InternalError ( model . ErrSignupFailed { } )
}
2022-05-03 15:26:32 +05:30
}
2025-03-06 15:39:45 +05:30
user := & types . User {
2025-04-15 21:05:36 +05:30
ID : uuid . New ( ) . String ( ) ,
2025-03-06 15:39:45 +05:30
Name : req . Name ,
Email : req . Email ,
Password : hash ,
TimeAuditable : types . TimeAuditable {
CreatedAt : time . Now ( ) ,
} ,
2023-08-12 10:10:25 +05:30
ProfilePictureURL : "" , // Currently unused
2025-04-26 15:50:02 +05:30
Role : invite . Role ,
2025-03-06 15:39:45 +05:30
OrgID : invite . OrgID ,
2022-05-03 15:26:32 +05:30
}
// TODO(Ahsan): Ideally create user and delete invitation should happen in a txn.
2025-04-26 15:50:02 +05:30
user , apiErr := dao . DB ( ) . CreateUser ( ctx , user , false )
2022-05-03 15:26:32 +05:30
if apiErr != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "CreateUser failed" , zap . Error ( apiErr . Err ) )
2022-10-06 20:13:30 +05:30
return nil , apiErr
}
2025-03-06 15:39:45 +05:30
apiErr = dao . DB ( ) . DeleteInvitation ( ctx , user . OrgID , user . Email )
2022-10-06 20:13:30 +05:30
if apiErr != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "delete invitation failed" , zap . Error ( apiErr . Err ) )
2022-10-06 20:13:30 +05:30
return nil , apiErr
}
2023-11-17 16:18:31 +05:30
telemetry . GetInstance ( ) . IdentifyUser ( user )
2024-03-28 21:43:41 +05:30
telemetry . GetInstance ( ) . SendEvent ( telemetry . TELEMETRY_EVENT_USER_INVITATION_ACCEPTED , nil , req . Email , true , false )
2023-11-17 16:18:31 +05:30
2022-10-06 20:13:30 +05:30
return user , nil
}
// 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.
2025-04-25 19:38:15 +05:30
func Register ( ctx context . Context , req * RegisterRequest , alertmanager alertmanager . Alertmanager , organizationModule organization . Module ) ( * types . User , * model . ApiError ) {
2022-10-06 20:13:30 +05:30
users , err := dao . DB ( ) . GetUsers ( ctx )
if err != nil {
return nil , model . InternalError ( fmt . Errorf ( "failed to get user count" ) )
2022-05-03 15:26:32 +05:30
}
2022-10-06 20:13:30 +05:30
switch len ( users ) {
case 0 :
2025-04-25 19:38:15 +05:30
user , err := RegisterFirstUser ( ctx , req , organizationModule )
2025-03-10 01:30:42 +05:30
if err != nil {
return nil , err
}
if err := alertmanager . SetDefaultConfig ( ctx , user . OrgID ) ; err != nil {
return nil , model . InternalError ( err )
}
return user , nil
2022-10-06 20:13:30 +05:30
default :
return RegisterInvitedUser ( ctx , req , false )
}
2022-05-03 15:26:32 +05:30
}
// Login method returns access and refresh tokens on successful login, else it errors out.
2025-02-17 18:16:41 +05:30
func Login ( ctx context . Context , request * model . LoginRequest , jwt * authtypes . JWT ) ( * model . LoginResponse , error ) {
user , err := authenticateLogin ( ctx , request , jwt )
2022-05-03 15:26:32 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Failed to authenticate login request" , zap . Error ( err ) )
2022-05-03 15:26:32 +05:30
return nil , err
}
2025-02-17 18:16:41 +05:30
userjwt , err := GenerateJWTForUser ( & user . User , jwt )
2022-05-03 15:26:32 +05:30
if err != nil {
2024-03-27 00:07:29 +05:30
zap . L ( ) . Error ( "Failed to generate JWT against login creds" , zap . Error ( err ) )
2022-10-06 20:13:30 +05:30
return nil , err
2022-05-03 15:26:32 +05:30
}
2023-11-17 16:18:31 +05:30
// ignoring identity for unnamed users as a patch for #3863
if user . Name != "" {
telemetry . GetInstance ( ) . IdentifyUser ( & user . User )
}
2023-01-07 02:54:27 +05:30
2022-05-03 15:26:32 +05:30
return & model . LoginResponse {
2022-10-06 20:13:30 +05:30
UserJwtObject : userjwt ,
2025-03-06 15:39:45 +05:30
UserId : user . User . ID ,
2022-05-03 15:26:32 +05:30
} , nil
}
2025-02-17 18:16:41 +05:30
// authenticateLogin is responsible for querying the DB and validating the credentials.
2025-03-06 15:39:45 +05:30
func authenticateLogin ( ctx context . Context , req * model . LoginRequest , jwt * authtypes . JWT ) ( * types . GettableUser , error ) {
2022-05-03 15:26:32 +05:30
// If refresh token is valid, then simply authorize the login request.
if len ( req . RefreshToken ) > 0 {
2025-02-17 18:16:41 +05:30
// parse the refresh token
claims , err := jwt . Claims ( req . RefreshToken )
2022-05-03 15:26:32 +05:30
if err != nil {
2025-02-17 18:16:41 +05:30
return nil , errors . Wrap ( err , "failed to parse refresh token" )
2022-05-03 15:26:32 +05:30
}
2025-04-26 15:50:02 +05:30
user := & types . GettableUser {
User : types . User {
ID : claims . UserID ,
Role : claims . Role . String ( ) ,
Email : claims . Email ,
OrgID : claims . OrgID ,
} ,
2025-02-17 18:16:41 +05:30
}
2022-05-03 15:26:32 +05:30
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.
2023-08-23 16:22:24 +05:30
func PasswordHash ( pass string ) ( string , error ) {
2022-05-03 15:26:32 +05:30
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 ) )
2024-06-11 20:10:38 +05:30
return err == nil
2022-05-03 15:26:32 +05:30
}
2022-10-06 20:13:30 +05:30
2025-03-06 15:39:45 +05:30
func GenerateJWTForUser ( user * types . User , jwt * authtypes . JWT ) ( model . UserJwtObject , error ) {
2025-04-26 15:50:02 +05:30
role , err := authtypes . NewRole ( user . Role )
if err != nil {
return model . UserJwtObject { } , err
}
accessJwt , accessClaims , err := jwt . AccessToken ( user . OrgID , user . ID , user . Email , role )
2022-10-06 20:13:30 +05:30
if err != nil {
2025-04-26 15:50:02 +05:30
return model . UserJwtObject { } , err
2022-10-06 20:13:30 +05:30
}
2025-04-26 15:50:02 +05:30
refreshJwt , refreshClaims , err := jwt . RefreshToken ( user . OrgID , user . ID , user . Email , role )
2022-10-06 20:13:30 +05:30
if err != nil {
2025-04-26 15:50:02 +05:30
return model . UserJwtObject { } , err
2022-10-06 20:13:30 +05:30
}
2025-04-26 15:50:02 +05:30
return model . UserJwtObject {
AccessJwt : accessJwt ,
RefreshJwt : refreshJwt ,
AccessJwtExpiry : accessClaims . ExpiresAt . Unix ( ) ,
RefreshJwtExpiry : refreshClaims . ExpiresAt . Unix ( ) ,
} , nil
}
func ValidatePassword ( password string ) error {
if len ( password ) < minimumPasswordLength {
return errors . Errorf ( "Password should be atleast %d characters." , minimumPasswordLength )
}
return nil
2022-10-06 20:13:30 +05:30
}