src.dualinventive.com/go/authentication-service/internal/authtokens/tokens.go

321 lines
10 KiB
Go

package authtokens
import (
"golang.org/x/crypto/bcrypt"
"src.dualinventive.com/go/authentication-service/internal/domain"
"src.dualinventive.com/go/authentication-service/internal/pwreset"
"src.dualinventive.com/go/authentication-service/internal/storage"
"src.dualinventive.com/go/lib/dilog"
)
//Service contains token related operations.
type Service struct {
Logger dilog.Logger
CredentialsRepository storage.CredentialsRepository
TokenRepository storage.TokenRepository
ResetCodeRepository storage.ResetCodeRepository
TemplateRepository pwreset.TemplateRepository
EmailSender pwreset.EmailSender
}
//NewService creates an authtokens.Service
func NewService(
logger dilog.Logger,
credentialsrepository storage.CredentialsRepository,
tokenrepository storage.TokenRepository,
resetcoderepository storage.ResetCodeRepository,
templaterepository pwreset.TemplateRepository,
emailsender pwreset.EmailSender,
) *Service {
service := &Service{
Logger: logger,
CredentialsRepository: credentialsrepository,
TokenRepository: tokenrepository,
ResetCodeRepository: resetcoderepository,
TemplateRepository: templaterepository,
EmailSender: emailsender,
}
return service
}
//CreateToken is used to create a new token for user
func (s *Service) CreateToken(
credentials domain.Credentials,
userAgent string,
) (token *domain.Token, err error) {
user, err := s.CredentialsRepository.GetUserByUserName(credentials.User, credentials.CompanyCode)
if err != nil {
s.Logger.WithError(err).WithField("user", credentials.User).WithField("company", credentials.CompanyCode).
Error("CredentialsRepository.GetUserByUserName returned error")
return nil, &ErrInvalidCredentials{}
}
if !validatePassword(credentials.Password, user.PasswordHash) {
return nil, &ErrInvalidCredentials{}
}
token, err = s.TokenRepository.
CreateToken(credentials.User, credentials.CompanyCode, userAgent, resolveRights(user)...)
if err != nil {
s.Logger.WithError(err).WithField("user", credentials.User).
WithField("company", credentials.CompanyCode).WithField("agent", userAgent).
Error("TokenRepository.CreateToken returned error")
return nil, NewErrRepositoryConnectionFailure("TokenRepository")
}
s.Logger.WithField("user", credentials.User).WithField("company", credentials.CompanyCode).
WithField("agent", userAgent).WithField("token", token.Secret[:20]+"...").
Debug("Service.CreateToken returned successful")
return token, nil
}
//resolveRights deduplicates rights of a user by turning them into a set-like map.
//This set is then returned as an array.
func resolveRights(user *domain.User) []string {
set := make(map[string]bool)
for _, role := range user.Roles {
for _, right := range role.Rights {
set[right.Code] = true
}
}
rights := make([]string, len(set))
for right := range set {
rights = append(rights, right)
}
return rights
}
//VerifyToken is used to verify if token is valid
func (s *Service) VerifyToken(
token *domain.Token,
rights ...string,
) (*domain.Token, error) {
if token == nil {
s.Logger.Warning("Service.VerifyToken got nil token")
return nil, &ErrNilToken{}
}
isValid, isAuthorized, err := s.TokenRepository.IsValid(token, rights...)
if !isValid {
switch err {
case storage.ErrTokenNotFound:
s.Logger.WithField("token", token).
Error("TokenRepository.IsValid returned ErrTokenNotFound")
return nil, &ErrInvalidToken{}
case storage.ErrTokenInvalid:
s.Logger.WithField("token", token).
Error("TokenRepository.IsValid returned ErrTokenInvalid")
return nil, &ErrInvalidToken{}
default:
s.Logger.WithField("token", token).
Error("TokenRepository.IsValid returned false")
return nil, &ErrInvalidToken{}
}
}
if isValid && !isAuthorized {
return nil, &ErrUnauthorized{}
}
s.Logger.WithField("token", token.Secret[:20]+"...").
Debug("Service.VerifyToken returned successful")
return token, nil
}
//DeleteToken is used to remove the token for user
func (s *Service) DeleteToken(
token *domain.Token,
) error {
if token == nil {
s.Logger.Warning("Service.DeleteToken got nil token")
return &ErrNilToken{}
}
err := s.TokenRepository.DeleteToken(token)
if err != nil {
s.Logger.WithError(err).WithField("token", token).
Error("TokenRepository.DeleteToken returned error")
return &ErrTokenNotFound{}
}
s.Logger.WithField("token", token.Secret[:20]+"...").
Debug("Service.DeleteToken returned successful")
return nil
}
//GetUserByToken is used to retrieve user information based on the given token.
func (s *Service) GetUserByToken(
token *domain.Token,
) (*domain.User, error) {
if token == nil {
s.Logger.Warning("Service.GetUserByToken got nil token")
return nil, &ErrNilToken{}
}
userName, companyCode, err := s.TokenRepository.GetUserNameByToken(token)
if err != nil {
s.Logger.WithError(err).WithField("token", token).
Error("TokenRepository.GetUserNameByToken returned error")
return nil, &ErrTokenNotFound{}
}
user, err := s.CredentialsRepository.GetUserByUserName(userName, companyCode)
if err != nil {
s.Logger.WithError(err).WithField("user", userName).WithField("company", companyCode).
Error("CredentialsRepository.GetUserByUserName returned error")
return nil, NewErrUserNotFound(userName)
}
if user == nil {
s.Logger.WithField("user", userName).WithField("company", companyCode).
Warning("CredentialsRepository.GetUserByUserName returned nil")
return nil, NewErrUserNotFound(userName)
}
s.Logger.WithField("token", token.Secret[:20]+"...").WithField("user", user).
Debug("Service.GetUserByToken returned successful")
return user, nil
}
//GetTokensByUser returns a list of currently active tokens by the given username.
func (s *Service) GetTokensByUser(userName string, companyCode string) ([]*domain.Token, error) {
if userName == "" {
s.Logger.WithField("user", userName).WithField("company", companyCode).
Warning("Service.GetTokensByUser got empty userName")
return nil, NewErrUserNotFound(userName)
}
if companyCode == "" {
s.Logger.WithField("user", userName).WithField("company", companyCode).
Warning("Service.GetTokensByUser got empty companyCode")
return nil, NewErrUserNotFound(userName)
}
tokens, err := s.TokenRepository.GetTokens(userName, companyCode)
if err != nil {
s.Logger.WithError(err).WithField("user", userName).WithField("company", companyCode).
Error("TokenRepository.GetTokens returned error")
return nil, NewErrFailedToGetTokens(userName, err)
}
s.Logger.WithField("user", userName).WithField("company", companyCode).WithField("token", tokens).
Debug("Service.GetTokensByUser returned successful")
return tokens, nil
}
//GetOpaqueTokensByToken returns a list of active session for the current user.
func (s *Service) GetOpaqueTokensByToken(token *domain.Token) (map[string]string, error) {
opaqueTokens := make(map[string]string)
if token == nil {
s.Logger.Warning("Service.GetOpaqueTokensByToken got nil token")
return nil, &ErrNilToken{}
}
isValid, _, err := s.TokenRepository.IsValid(token)
if err != nil {
s.Logger.WithError(err).WithField("token", token).
Error("TokenRepository.IsValid returned error")
return nil, &ErrInvalidToken{}
}
if !isValid {
s.Logger.WithField("token", token).
Warning("TokenRepository.IsValid returned false")
return nil, &ErrInvalidToken{}
}
userName, companyCode, err := s.TokenRepository.GetUserNameByToken(token)
if err != nil {
s.Logger.WithError(err).WithField("token", token).
Error("TokenRepository.GetUserNameByToken returned error")
return nil, NewErrRepositoryConnectionFailure("TokenRepository")
}
tokens, err := s.GetTokensByUser(userName, companyCode)
if err != nil {
s.Logger.WithError(err).WithField("user", userName).WithField("company", companyCode).
Error("GetTokensByUser returned error")
// err is NewErrUserNotFound or NewErrFailedToGetTokens
return nil, err
}
for _, token := range tokens {
opaqueID, userAgent, err := s.TokenRepository.GetTokenPayload(token)
if err != nil {
s.Logger.WithError(err).WithField("token", token).
Error("TokenRepository.GetTokenPayload returned error")
} else {
opaqueTokens[opaqueID] = userAgent
}
}
s.Logger.WithField("token", token.Secret[:20]+"...").WithField("opaque", opaqueTokens).
Debug("Service.GetOpaqueTokensByToken returned successful")
return opaqueTokens, nil
}
//DeleteTokenByOpaqueID removes a token by reference of it's opaque id.
func (s *Service) DeleteTokenByOpaqueID(token *domain.Token, opaqueTokenID string) error {
if token == nil {
s.Logger.Warning("Service.DeleteTokenByOpaqueID got nil token")
return &ErrNilToken{}
}
isValid, _, err := s.TokenRepository.IsValid(token)
if err != nil {
s.Logger.WithError(err).WithField("token", token).
Error("TokenRepository.IsValid returned error")
return &ErrInvalidToken{}
}
if !isValid {
s.Logger.WithField("token", token).
Warning("TokenRepository.IsValid returned false")
return &ErrInvalidToken{}
}
tokenMap := make(map[string]*domain.Token)
userName, companyCode, err := s.TokenRepository.GetUserNameByToken(token)
if err != nil {
s.Logger.WithError(err).WithField("token", token).
Error("TokenRepository.GetUserNameByToken returned error")
return NewErrRepositoryConnectionFailure("TokenRepository")
}
tokens, err := s.GetTokensByUser(userName, companyCode)
if err != nil {
s.Logger.WithError(err).WithField("user", userName).WithField("company", companyCode).
Error("TokenRepository.GetTokensByUser returned error")
// err is NewErrUserNotFound or NewErrFailedToGetTokens
return err
}
for _, token := range tokens {
opaqueID, _, err := s.TokenRepository.GetTokenPayload(token) //nolint: vetshadow
if err != nil {
s.Logger.WithError(err).WithField("token", token).
Error("TokenRepository.GetTokenPayload returned error")
} else {
tokenMap[opaqueID] = token
}
}
token, ok := tokenMap[opaqueTokenID]
if !ok {
s.Logger.Warning("Service.DeleteTokenByOpaqueID token not found")
return &ErrTokenNotFound{}
}
err = s.DeleteToken(token)
if err != nil {
// err is ErrNilToken or ErrTokenNotFound
return err
}
s.Logger.WithField("token", token.Secret[:20]+"...").WithField("opaque", opaqueTokenID).
Debug("Service.DeleteTokenByOpaqueID returned successful")
return nil
}
func validatePassword(password string, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}