github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/endpoint/auth.go (about)

     1  // This file is part of the Smart Home
     2  // Program complex distribution https://github.com/e154/smart-home
     3  // Copyright (C) 2016-2023, Filippov Alex
     4  //
     5  // This library is free software: you can redistribute it and/or
     6  // modify it under the terms of the GNU Lesser General Public
     7  // License as published by the Free Software Foundation; either
     8  // version 3 of the License, or (at your option) any later version.
     9  //
    10  // This library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13  // Library General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public
    16  // License along with this library.  If not, see
    17  // <https://www.gnu.org/licenses/>.
    18  
    19  package endpoint
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"time"
    25  
    26  	"github.com/pkg/errors"
    27  
    28  	"github.com/e154/smart-home/common/apperr"
    29  	"github.com/e154/smart-home/common/events"
    30  	m "github.com/e154/smart-home/models"
    31  	"github.com/e154/smart-home/plugins/email"
    32  	"github.com/e154/smart-home/plugins/notify"
    33  	"github.com/e154/smart-home/plugins/notify/common"
    34  	"github.com/e154/smart-home/system/access_list"
    35  )
    36  
    37  const (
    38  	// AdminId ...
    39  	AdminId = 1
    40  )
    41  
    42  // AuthEndpoint ...
    43  type AuthEndpoint struct {
    44  	*CommonEndpoint
    45  }
    46  
    47  // NewAuthEndpoint ...
    48  func NewAuthEndpoint(common *CommonEndpoint) *AuthEndpoint {
    49  	return &AuthEndpoint{
    50  		CommonEndpoint: common,
    51  	}
    52  }
    53  
    54  // SignIn ...
    55  func (a *AuthEndpoint) SignIn(ctx context.Context, email, password string, ip string) (user *m.User, accessToken string, err error) {
    56  
    57  	if user, err = a.adaptors.User.GetByEmail(ctx, email); err != nil {
    58  		err = errors.Wrap(apperr.ErrUnauthorized, fmt.Sprintf("email %s", email))
    59  		return
    60  	} else if !user.CheckPass(password) {
    61  		err = apperr.ErrPassNotValid
    62  		return
    63  	} else if user.Status == "blocked" && user.Id != AdminId {
    64  		err = apperr.ErrAccountIsBlocked
    65  		return
    66  	}
    67  
    68  	if accessToken, err = a.jwtManager.Generate(user, false); err != nil {
    69  		err = errors.Wrap(apperr.ErrUnauthorized, err.Error())
    70  		return
    71  	}
    72  
    73  	if err = a.adaptors.User.SignIn(ctx, user, ip); err != nil {
    74  		err = errors.Wrap(apperr.ErrUnauthorized, err.Error())
    75  		return
    76  	}
    77  
    78  	log.Infof("Successful login, user: %s", user.Email)
    79  
    80  	a.eventBus.Publish(fmt.Sprintf("system/users/%d", user.Id), events.EventUserSignedIn{
    81  		User: user,
    82  	})
    83  
    84  	return
    85  }
    86  
    87  // SignOut ...
    88  func (a *AuthEndpoint) SignOut(ctx context.Context, user *m.User) (err error) {
    89  	err = a.adaptors.User.ClearToken(ctx, user)
    90  	if err != nil {
    91  		err = errors.Wrap(apperr.ErrNotAllowed, err.Error())
    92  		return
    93  	}
    94  	return
    95  }
    96  
    97  // PasswordReset ...
    98  func (a *AuthEndpoint) PasswordReset(ctx context.Context, userEmail string, token, newPassword *string) (err error) {
    99  
   100  	if token != nil {
   101  
   102  		if newPassword == nil {
   103  			err = errors.New("password is required")
   104  			return
   105  		}
   106  
   107  		var user *m.User
   108  		if user, err = a.adaptors.User.GetByResetPassToken(ctx, *token); err != nil {
   109  			return
   110  		}
   111  
   112  		if err = user.SetPass(*newPassword); err != nil {
   113  			return
   114  		}
   115  
   116  		user.ResetPasswordToken = ""
   117  		user.ResetPasswordSentAt = nil
   118  		if err = a.adaptors.User.Update(ctx, user); err == nil {
   119  			log.Warnf("The password for the %s user has just been updated", user.Email)
   120  		}
   121  
   122  		return
   123  	}
   124  
   125  	var user *m.User
   126  	if user, err = a.adaptors.User.GetByEmail(ctx, userEmail); err != nil {
   127  		err = errors.Wrap(apperr.ErrNotAllowed, err.Error())
   128  		return
   129  	}
   130  
   131  	if user.ResetPasswordSentAt != nil && time.Now().Before(*user.ResetPasswordSentAt) {
   132  		err = errors.Wrap(apperr.ErrNotAllowed, "reset request already exists")
   133  		return
   134  	}
   135  
   136  	var resetToken string
   137  	if resetToken, err = a.adaptors.User.GenResetPassToken(ctx, user); err != nil {
   138  		err = errors.Wrap(apperr.ErrNotAllowed, err.Error())
   139  		return
   140  	}
   141  
   142  	var variable m.Variable
   143  	if variable, err = a.adaptors.Variable.GetByName(ctx, "serverUrl"); err != nil {
   144  		err = errors.Wrap(apperr.ErrVariableGet, err.Error())
   145  		return
   146  	}
   147  
   148  	renderParams := map[string]interface{}{
   149  		"site:name":               "Smart home",
   150  		"user:name:first":         user.FirstName,
   151  		"user:name:last":          user.LastName,
   152  		"user:one-time-login-url": fmt.Sprintf("%s/#/password_reset?t=%s", variable.Value, resetToken),
   153  	}
   154  
   155  	var render *m.TemplateRender
   156  	if render, err = a.adaptors.Template.Render(ctx, "password_reset", renderParams); err != nil {
   157  		return
   158  	}
   159  
   160  	a.eventBus.Publish(notify.TopicNotify, common.Message{
   161  		Type: email.Name,
   162  		Attributes: map[string]interface{}{
   163  			email.AttrAddresses: user.Email,
   164  			email.AttrSubject:   "Reset your Smart home password",
   165  			email.AttrBody:      render.Body,
   166  		},
   167  	})
   168  
   169  	return
   170  }
   171  
   172  // AccessList ...
   173  func (a *AuthEndpoint) AccessList(ctx context.Context, user *m.User, accessListService access_list.AccessListService) (accessList *access_list.AccessList, err error) {
   174  	accessList = accessListService.List(ctx)
   175  	return
   176  }