1
0
mirror of https://github.com/mainflux/mainflux.git synced 2025-05-01 13:48:56 +08:00
Ivan Milošević 0ab627730f MF-538 - Improve logging and API errors (#866)
* user service - wraping errors

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* wrapping more errors
unwrap only wrapped errors

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add internal database error
Wrap internal database error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Wrap user not found error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Wrapping errors in idp and hasher

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Use error.Is for testing errors in Identify test

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Get wraper from wrapped errors

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Switch order of wrapping errors
Remove dead code (comments)

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* assert true in tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Change comparing errors in tests (assert.True)

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add errorRes structure to API responses in body in things service

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* resolve conflicts after rebasing

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Create errors package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* implement new errors package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Modify tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* return copyright comments

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove changes from .gitignore

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Move logging to encode errors
Comment exported vars and methods
Formatting

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Login function returns errors.Error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Modify login tests to meet login returning errors.Error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Error interface

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Change parameter in Wrapper to interface Error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* implement new error interface

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Modify tests to use new Error interface

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix Login

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove unnecessary errir casting

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* new error interface implementation

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* check if Error is empty in registrationEndpoint

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add Empty factory function
Use new Empty factory function
Use isEmpty method

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Lose else in encodeError

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Modify tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove *json.UnmarshalTypeError and *json.SyntaxError types from encodeError type switch

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix nil error value in jwtIdentityProvider

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix gprc to use new error package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* rename receiver in errors package
grpc errors

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove debugging code

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Resolving conflicts after rebase

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove comment

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove Empty from custom error
Implement custom error on new methods

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* WIP tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove wrap from Error interface

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* password-change related tests
remove debug code

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove dead code

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Move all errors casting to errors package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix comment in error package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Change struct pointer to interface in package methods

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* resolving reviews

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix return in database.go

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix copyright header

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix comment in hasher

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-11-20 14:43:41 +01:00

228 lines
6.7 KiB
Go

// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package users
import (
"context"
"github.com/mainflux/mainflux/errors"
)
var (
// ErrConflict indicates usage of the existing email during account
// registration.
ErrConflict = errors.New("email already taken")
// ErrMalformedEntity indicates malformed entity specification
// (e.g. invalid username or password).
ErrMalformedEntity = errors.New("malformed entity specification")
// ErrUnauthorizedAccess indicates missing or invalid credentials provided
// when accessing a protected resource.
ErrUnauthorizedAccess = errors.New("missing or invalid credentials provided")
// ErrNotFound indicates a non-existent entity request
ErrNotFound = errors.New("non-existent entity")
// ErrUserNotFound indicates a non-existent user request
ErrUserNotFound = errors.New("non-existent user")
// ErrScanMetadata indicates problem with metadata in db
ErrScanMetadata = errors.New("Failed to scan metadata")
// ErrMissingEmail indicates missing email for password reset request
ErrMissingEmail = errors.New("missing email for password reset")
// ErrMissingResetToken indicates malformed or missing reset token
// for reseting password
ErrMissingResetToken = errors.New("error missing reset token")
// ErrGeneratingResetToken indicates error in generating password recovery
// token
ErrGeneratingResetToken = errors.New("error missing reset token")
// ErrGetToken indicates error in getting signed token
ErrGetToken = errors.New("Get signed token failed")
)
// Service specifies an API that must be fullfiled by the domain service
// implementation, and all of its decorators (e.g. logging & metrics).
type Service interface {
// Register creates new user account. In case of the failed registration, a
// non-nil error value is returned.
Register(context.Context, User) errors.Error
// Login authenticates the user given its credentials. Successful
// authentication generates new access token. Failed invocations are
// identified by the non-nil error values in the response.
Login(context.Context, User) (string, errors.Error)
// Identify validates user's token. If token is valid, user's id
// is returned. If token is invalid, or invocation failed for some
// other reason, non-nil error values are returned in response.
Identify(string) (string, errors.Error)
// Get authenticated user info for the given token
UserInfo(ctx context.Context, token string) (User, errors.Error)
// UpdateUser updates the user metadata
UpdateUser(ctx context.Context, token string, user User) errors.Error
// GenerateResetToken email where mail will be sent.
// host is used for generating reset link.
GenerateResetToken(_ context.Context, email, host string) errors.Error
// ChangePassword change users password for authenticated user.
ChangePassword(_ context.Context, authToken, password, oldPassword string) errors.Error
// ResetPassword change users password in reset flow.
// token can be authentication token or password reset token.
ResetPassword(_ context.Context, resetToken, password string) errors.Error
//SendPasswordReset sends reset password link to email
SendPasswordReset(_ context.Context, host, email, token string) errors.Error
}
var _ Service = (*usersService)(nil)
type usersService struct {
users UserRepository
hasher Hasher
idp IdentityProvider
token Tokenizer
email Emailer
}
// New instantiates the users service implementation
func New(users UserRepository, hasher Hasher, idp IdentityProvider, m Emailer, t Tokenizer) Service {
return &usersService{users: users, hasher: hasher, idp: idp, email: m, token: t}
}
func (svc usersService) Register(ctx context.Context, user User) errors.Error {
hash, err := svc.hasher.Hash(user.Password)
if err != nil {
return errors.Wrap(ErrMalformedEntity, err)
}
user.Password = hash
return svc.users.Save(ctx, user)
}
func (svc usersService) Login(ctx context.Context, user User) (string, errors.Error) {
dbUser, err := svc.users.RetrieveByID(ctx, user.Email)
if err != nil {
return "", errors.Wrap(ErrUnauthorizedAccess, err)
}
if err := svc.hasher.Compare(user.Password, dbUser.Password); err != nil {
return "", errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.idp.TemporaryKey(user.Email)
}
func (svc usersService) Identify(token string) (string, errors.Error) {
id, err := svc.idp.Identity(token)
if err != nil {
return "", errors.Wrap(ErrUnauthorizedAccess, err)
}
return id, nil
}
func (svc usersService) UserInfo(ctx context.Context, token string) (User, errors.Error) {
id, err := svc.idp.Identity(token)
if err != nil {
return User{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
dbUser, err := svc.users.RetrieveByID(ctx, id)
if err != nil {
return User{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
return User{
Email: id,
Password: "",
Metadata: dbUser.Metadata,
}, nil
}
func (svc usersService) UpdateUser(ctx context.Context, token string, u User) errors.Error {
email, err := svc.idp.Identity(token)
if err != nil {
return ErrUnauthorizedAccess
}
user := User{
Email: email,
Metadata: u.Metadata,
}
return svc.users.UpdateUser(ctx, user)
}
func (svc usersService) GenerateResetToken(ctx context.Context, email, host string) errors.Error {
user, err := svc.users.RetrieveByID(ctx, email)
if err != nil || user.Email == "" {
return ErrUserNotFound
}
tok, err := svc.token.Generate(email, 0)
if err != nil {
return errors.Wrap(ErrGeneratingResetToken, err)
}
return svc.SendPasswordReset(ctx, host, email, tok)
}
func (svc usersService) ResetPassword(ctx context.Context, resetToken, password string) errors.Error {
email, err := svc.token.Verify(resetToken)
if err != nil {
return err
}
u, err := svc.users.RetrieveByID(ctx, email)
if err != nil || u.Email == "" {
return ErrUserNotFound
}
password, err = svc.hasher.Hash(password)
if err != nil {
return err
}
return svc.users.UpdatePassword(ctx, email, password)
}
func (svc usersService) ChangePassword(ctx context.Context, authToken, password, oldPassword string) errors.Error {
email, err := svc.idp.Identity(authToken)
if err != nil {
return errors.Wrap(ErrUnauthorizedAccess, err)
}
u := User{
Email: email,
Password: oldPassword,
}
if _, err = svc.Login(ctx, u); err != nil {
return ErrUnauthorizedAccess
}
u, err = svc.users.RetrieveByID(ctx, email)
if err != nil || u.Email == "" {
return ErrUserNotFound
}
password, err = svc.hasher.Hash(password)
if err != nil {
return err
}
return svc.users.UpdatePassword(ctx, email, password)
}
// SendPasswordReset sends password recovery link to user
func (svc usersService) SendPasswordReset(_ context.Context, host, email, token string) errors.Error {
to := []string{email}
return svc.email.SendPasswordReset(to, host, token)
}