mirror of
https://github.com/mainflux/mainflux.git
synced 2025-05-01 13:48:56 +08:00

* 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>
228 lines
6.7 KiB
Go
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)
|
|
}
|