321 lines
10 KiB
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
|
|
}
|