github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/service/auth.go (about)

     1  package service
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"gorm.io/gorm"
     9  
    10  	"github.com/pyroscope-io/pyroscope/pkg/model"
    11  )
    12  
    13  type AuthService struct {
    14  	db              *gorm.DB
    15  	userService     UserService
    16  	apiKeyService   APIKeyService
    17  	jwtTokenService JWTTokenService
    18  }
    19  
    20  func NewAuthService(db *gorm.DB, jwtTokenService JWTTokenService, apiKeyService APIKeyService) AuthService {
    21  	return AuthService{
    22  		db:              db,
    23  		userService:     NewUserService(db),
    24  		apiKeyService:   apiKeyService,
    25  		jwtTokenService: jwtTokenService,
    26  	}
    27  }
    28  
    29  // AuthenticateUser returns User with the given login if its password hash
    30  // matches the given one. If user cannot be found or the password does not
    31  // match the function returns ErrCredentialsInvalid.
    32  //
    33  // External users are not allowed to use password authentication.
    34  // TODO(kolesnikovae): It's true for "some" authentication providers.
    35  // Others may need us to pass through the credentials (e.g. LDAP).
    36  func (svc AuthService) AuthenticateUser(ctx context.Context, name string, password string) (model.User, error) {
    37  	user, err := svc.userService.FindUserByName(ctx, name)
    38  	switch {
    39  	case err == nil:
    40  	case errors.Is(err, model.ErrUserNotFound):
    41  		return model.User{}, model.ErrCredentialsInvalid
    42  	default:
    43  		return model.User{}, err
    44  	}
    45  	if model.IsUserExternal(user) {
    46  		// TODO(kolesnikovae): We could be a bit more specific here
    47  		//  disclosing some info: e.g. tell which auth provider to use.
    48  		return model.User{}, model.ErrCredentialsInvalid
    49  	}
    50  	if model.IsUserDisabled(user) {
    51  		return model.User{}, model.ErrUserDisabled
    52  	}
    53  	if err = model.VerifyPassword(user.PasswordHash, password); err != nil {
    54  		return model.User{}, model.ErrCredentialsInvalid
    55  	}
    56  	return user, nil
    57  }
    58  
    59  func (svc AuthService) APIKeyFromToken(ctx context.Context, t string) (model.APIKey, error) {
    60  	id, secret, err := model.DecodeAPIKey(t)
    61  	if err != nil {
    62  		return model.APIKey{}, err
    63  	}
    64  	apiKey, err := svc.apiKeyService.FindAPIKeyByID(ctx, id)
    65  	if err != nil {
    66  		return model.APIKey{}, err
    67  	}
    68  	if err = apiKey.Verify(secret); err != nil {
    69  		return model.APIKey{}, err
    70  	}
    71  	return apiKey, nil
    72  }
    73  
    74  func (svc AuthService) UserFromJWTToken(ctx context.Context, t string) (model.User, error) {
    75  	token, err := svc.jwtTokenService.Parse(t)
    76  	if err != nil {
    77  		return model.User{}, fmt.Errorf("invalid jwt token: %w", err)
    78  	}
    79  	userToken, ok := svc.jwtTokenService.UserFromJWTToken(token)
    80  	if !ok {
    81  		return model.User{}, fmt.Errorf("user token is invalid")
    82  	}
    83  	user, err := svc.userService.FindUserByName(ctx, userToken.Name)
    84  	if err != nil {
    85  		return model.User{}, err
    86  	}
    87  	if model.IsUserDisabled(user) {
    88  		return model.User{}, model.ErrUserDisabled
    89  	}
    90  	if user.Role != userToken.Role {
    91  		return model.User{}, fmt.Errorf("user role has changed")
    92  	}
    93  	return user, nil
    94  }