/users/service.go

https://github.com/mainflux/mainflux · Go · 395 lines · 283 code · 63 blank · 49 comment · 73 complexity · c1e26971beedd01997afc6596db02858 MD5 · raw file

  1. // Copyright (c) Mainflux
  2. // SPDX-License-Identifier: Apache-2.0
  3. package users
  4. import (
  5. "context"
  6. "regexp"
  7. "github.com/mainflux/mainflux"
  8. "github.com/mainflux/mainflux/authn"
  9. "github.com/mainflux/mainflux/pkg/errors"
  10. uuidProvider "github.com/mainflux/mainflux/pkg/uuid"
  11. )
  12. var (
  13. groupRegexp = regexp.MustCompile("^[a-zA-Z0-9]+$")
  14. // ErrConflict indicates usage of the existing email during account
  15. // registration.
  16. ErrConflict = errors.New("email already taken")
  17. // ErrGroupConflict indicates group name already taken.
  18. ErrGroupConflict = errors.New("group already exists")
  19. // ErrMalformedEntity indicates malformed entity specification
  20. // (e.g. invalid username or password).
  21. ErrMalformedEntity = errors.New("malformed entity specification")
  22. // ErrUnauthorizedAccess indicates missing or invalid credentials provided
  23. // when accessing a protected resource.
  24. ErrUnauthorizedAccess = errors.New("missing or invalid credentials provided")
  25. // ErrNotFound indicates a non-existent entity request.
  26. ErrNotFound = errors.New("non-existent entity")
  27. // ErrUserNotFound indicates a non-existent user request.
  28. ErrUserNotFound = errors.New("non-existent user")
  29. // ErrScanMetadata indicates problem with metadata in db.
  30. ErrScanMetadata = errors.New("failed to scan metadata")
  31. // ErrMissingEmail indicates missing email for password reset request.
  32. ErrMissingEmail = errors.New("missing email for password reset")
  33. // ErrMissingResetToken indicates malformed or missing reset token
  34. // for reseting password.
  35. ErrMissingResetToken = errors.New("missing reset token")
  36. // ErrRecoveryToken indicates error in generating password recovery token.
  37. ErrRecoveryToken = errors.New("failed to generate password recovery token")
  38. // ErrGetToken indicates error in getting signed token.
  39. ErrGetToken = errors.New("failed to fetch signed token")
  40. // ErrCreateUser indicates error in creating user.
  41. ErrCreateUser = errors.New("failed to create user")
  42. // ErrCreateGroup indicates error in creating group.
  43. ErrCreateGroup = errors.New("failed to create group")
  44. // ErrDeleteGroupMissing indicates in delete operation that group doesnt exist.
  45. ErrDeleteGroupMissing = errors.New("group is not existing, already deleted")
  46. // ErrAssignUserToGroup indicates an error in assigning user to a group.
  47. ErrAssignUserToGroup = errors.New("failed assigning user to a group")
  48. )
  49. // Service specifies an API that must be fullfiled by the domain service
  50. // implementation, and all of its decorators (e.g. logging & metrics).
  51. type Service interface {
  52. // Register creates new user account. In case of the failed registration, a
  53. // non-nil error value is returned.
  54. Register(ctx context.Context, user User) (string, error)
  55. // Login authenticates the user given its credentials. Successful
  56. // authentication generates new access token. Failed invocations are
  57. // identified by the non-nil error values in the response.
  58. Login(ctx context.Context, user User) (string, error)
  59. // User authenticated user info for the given token.
  60. User(ctx context.Context, token string) (User, error)
  61. // UpdateUser updates the user metadata.
  62. UpdateUser(ctx context.Context, token string, user User) error
  63. // GenerateResetToken email where mail will be sent.
  64. // host is used for generating reset link.
  65. GenerateResetToken(ctx context.Context, email, host string) error
  66. // ChangePassword change users password for authenticated user.
  67. ChangePassword(ctx context.Context, authToken, password, oldPassword string) error
  68. // ResetPassword change users password in reset flow.
  69. // token can be authentication token or password reset token.
  70. ResetPassword(ctx context.Context, resetToken, password string) error
  71. //SendPasswordReset sends reset password link to email.
  72. SendPasswordReset(ctx context.Context, host, email, token string) error
  73. // CreateGroup creates new user group.
  74. CreateGroup(ctx context.Context, token string, group Group) (Group, error)
  75. // UpdateGroup updates the group identified by the provided ID.
  76. UpdateGroup(ctx context.Context, token string, group Group) error
  77. // Group retrieves data about the group identified by ID.
  78. Group(ctx context.Context, token, id string) (Group, error)
  79. // ListGroups retrieves groups that are children to group identified by parenID
  80. // if parentID is empty all groups are listed.
  81. Groups(ctx context.Context, token, parentID string, offset, limit uint64, meta Metadata) (GroupPage, error)
  82. // Members retrieves users that are assigned to a group identified by groupID.
  83. Members(ctx context.Context, token, groupID string, offset, limit uint64, meta Metadata) (UserPage, error)
  84. // Memberships retrieves groups that user identified with userID belongs to.
  85. Memberships(ctx context.Context, token, groupID string, offset, limit uint64, meta Metadata) (GroupPage, error)
  86. // RemoveGroup removes the group identified with the provided ID.
  87. RemoveGroup(ctx context.Context, token, id string) error
  88. // Assign adds user with userID into the group identified by groupID.
  89. Assign(ctx context.Context, token, userID, groupID string) error
  90. // Unassign removes user with userID from group identified by groupID.
  91. Unassign(ctx context.Context, token, userID, groupID string) error
  92. }
  93. // PageMetadata contains page metadata that helps navigation.
  94. type PageMetadata struct {
  95. Total uint64
  96. Offset uint64
  97. Limit uint64
  98. Name string
  99. }
  100. type GroupPage struct {
  101. PageMetadata
  102. Groups []Group
  103. }
  104. type UserPage struct {
  105. PageMetadata
  106. Users []User
  107. }
  108. var _ Service = (*usersService)(nil)
  109. type usersService struct {
  110. users UserRepository
  111. groups GroupRepository
  112. hasher Hasher
  113. email Emailer
  114. auth mainflux.AuthNServiceClient
  115. }
  116. // New instantiates the users service implementation
  117. func New(users UserRepository, groups GroupRepository, hasher Hasher, auth mainflux.AuthNServiceClient, m Emailer) Service {
  118. return &usersService{
  119. users: users,
  120. groups: groups,
  121. hasher: hasher,
  122. auth: auth,
  123. email: m,
  124. }
  125. }
  126. func (svc usersService) Register(ctx context.Context, user User) (string, error) {
  127. if err := user.Validate(); err != nil {
  128. return "", err
  129. }
  130. hash, err := svc.hasher.Hash(user.Password)
  131. if err != nil {
  132. return "", errors.Wrap(ErrMalformedEntity, err)
  133. }
  134. user.Password = hash
  135. uid, err := uuidProvider.New().ID()
  136. if err != nil {
  137. return "", errors.Wrap(ErrCreateUser, err)
  138. }
  139. user.ID = uid
  140. uid, err = svc.users.Save(ctx, user)
  141. if err != nil {
  142. return "", err
  143. }
  144. return uid, nil
  145. }
  146. func (svc usersService) Login(ctx context.Context, user User) (string, error) {
  147. dbUser, err := svc.users.RetrieveByEmail(ctx, user.Email)
  148. if err != nil {
  149. return "", errors.Wrap(ErrUnauthorizedAccess, err)
  150. }
  151. if err := svc.hasher.Compare(user.Password, dbUser.Password); err != nil {
  152. return "", errors.Wrap(ErrUnauthorizedAccess, err)
  153. }
  154. return svc.issue(ctx, dbUser.Email, authn.UserKey)
  155. }
  156. func (svc usersService) User(ctx context.Context, token string) (User, error) {
  157. email, err := svc.identify(ctx, token)
  158. if err != nil {
  159. return User{}, err
  160. }
  161. dbUser, err := svc.users.RetrieveByEmail(ctx, email)
  162. if err != nil {
  163. return User{}, errors.Wrap(ErrUnauthorizedAccess, err)
  164. }
  165. return User{
  166. ID: dbUser.ID,
  167. Email: email,
  168. Password: "",
  169. Metadata: dbUser.Metadata,
  170. }, nil
  171. }
  172. func (svc usersService) ListUsers(ctx context.Context, token string, groupID string, offset, limit uint64, um Metadata) (UserPage, error) {
  173. _, err := svc.identify(ctx, token)
  174. if err != nil {
  175. return UserPage{}, err
  176. }
  177. return svc.users.Members(ctx, groupID, offset, limit, um)
  178. }
  179. func (svc usersService) UpdateUser(ctx context.Context, token string, u User) error {
  180. email, err := svc.identify(ctx, token)
  181. if err != nil {
  182. return errors.Wrap(ErrUnauthorizedAccess, err)
  183. }
  184. user := User{
  185. Email: email,
  186. Metadata: u.Metadata,
  187. }
  188. return svc.users.UpdateUser(ctx, user)
  189. }
  190. func (svc usersService) GenerateResetToken(ctx context.Context, email, host string) error {
  191. user, err := svc.users.RetrieveByEmail(ctx, email)
  192. if err != nil || user.Email == "" {
  193. return ErrUserNotFound
  194. }
  195. t, err := svc.issue(ctx, email, authn.RecoveryKey)
  196. if err != nil {
  197. return errors.Wrap(ErrRecoveryToken, err)
  198. }
  199. return svc.SendPasswordReset(ctx, host, email, t)
  200. }
  201. func (svc usersService) ResetPassword(ctx context.Context, resetToken, password string) error {
  202. email, err := svc.identify(ctx, resetToken)
  203. if err != nil {
  204. return errors.Wrap(ErrUnauthorizedAccess, err)
  205. }
  206. u, err := svc.users.RetrieveByEmail(ctx, email)
  207. if err != nil || u.Email == "" {
  208. return ErrUserNotFound
  209. }
  210. password, err = svc.hasher.Hash(password)
  211. if err != nil {
  212. return err
  213. }
  214. return svc.users.UpdatePassword(ctx, email, password)
  215. }
  216. func (svc usersService) ChangePassword(ctx context.Context, authToken, password, oldPassword string) error {
  217. email, err := svc.identify(ctx, authToken)
  218. if err != nil {
  219. return errors.Wrap(ErrUnauthorizedAccess, err)
  220. }
  221. u := User{
  222. Email: email,
  223. Password: oldPassword,
  224. }
  225. if _, err := svc.Login(ctx, u); err != nil {
  226. return ErrUnauthorizedAccess
  227. }
  228. u, err = svc.users.RetrieveByEmail(ctx, email)
  229. if err != nil || u.Email == "" {
  230. return ErrUserNotFound
  231. }
  232. password, err = svc.hasher.Hash(password)
  233. if err != nil {
  234. return err
  235. }
  236. return svc.users.UpdatePassword(ctx, email, password)
  237. }
  238. // SendPasswordReset sends password recovery link to user
  239. func (svc usersService) SendPasswordReset(_ context.Context, host, email, token string) error {
  240. to := []string{email}
  241. return svc.email.SendPasswordReset(to, host, token)
  242. }
  243. func (svc usersService) identify(ctx context.Context, token string) (string, error) {
  244. email, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
  245. if err != nil {
  246. return "", errors.Wrap(ErrUnauthorizedAccess, err)
  247. }
  248. return email.GetValue(), nil
  249. }
  250. func (svc usersService) CreateGroup(ctx context.Context, token string, group Group) (Group, error) {
  251. if group.Name == "" || !groupRegexp.MatchString(group.Name) {
  252. return Group{}, ErrMalformedEntity
  253. }
  254. userID, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
  255. if err != nil {
  256. return Group{}, errors.Wrap(ErrUnauthorizedAccess, err)
  257. }
  258. user, err := svc.users.RetrieveByEmail(ctx, userID.Value)
  259. if err != nil {
  260. return Group{}, errors.Wrap(ErrUnauthorizedAccess, err)
  261. }
  262. uid, err := uuidProvider.New().ID()
  263. if err != nil {
  264. return Group{}, errors.Wrap(ErrCreateUser, err)
  265. }
  266. group.ID = uid
  267. group.OwnerID = user.ID
  268. return svc.groups.Save(ctx, group)
  269. }
  270. func (svc usersService) Groups(ctx context.Context, token string, parentID string, offset, limit uint64, meta Metadata) (GroupPage, error) {
  271. _, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
  272. if err != nil {
  273. return GroupPage{}, errors.Wrap(ErrUnauthorizedAccess, err)
  274. }
  275. return svc.groups.RetrieveAllWithAncestors(ctx, parentID, offset, limit, meta)
  276. }
  277. func (svc usersService) Members(ctx context.Context, token, groupID string, offset, limit uint64, meta Metadata) (UserPage, error) {
  278. _, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
  279. if err != nil {
  280. return UserPage{}, errors.Wrap(ErrUnauthorizedAccess, err)
  281. }
  282. return svc.users.Members(ctx, groupID, offset, limit, meta)
  283. }
  284. func (svc usersService) RemoveGroup(ctx context.Context, token, id string) error {
  285. _, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
  286. if err != nil {
  287. return errors.Wrap(ErrUnauthorizedAccess, err)
  288. }
  289. return svc.groups.Delete(ctx, id)
  290. }
  291. func (svc usersService) Unassign(ctx context.Context, token, userID, groupID string) error {
  292. _, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
  293. if err != nil {
  294. return errors.Wrap(ErrUnauthorizedAccess, err)
  295. }
  296. return svc.groups.Unassign(ctx, userID, groupID)
  297. }
  298. func (svc usersService) UpdateGroup(ctx context.Context, token string, group Group) error {
  299. _, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
  300. if err != nil {
  301. return errors.Wrap(ErrUnauthorizedAccess, err)
  302. }
  303. return svc.groups.Update(ctx, group)
  304. }
  305. func (svc usersService) Group(ctx context.Context, token, id string) (Group, error) {
  306. _, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
  307. if err != nil {
  308. return Group{}, errors.Wrap(ErrUnauthorizedAccess, err)
  309. }
  310. return svc.groups.RetrieveByID(ctx, id)
  311. }
  312. func (svc usersService) Assign(ctx context.Context, token, userID, groupID string) error {
  313. _, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
  314. if err != nil {
  315. return errors.Wrap(ErrUnauthorizedAccess, err)
  316. }
  317. return svc.groups.Assign(ctx, userID, groupID)
  318. }
  319. func (svc usersService) issue(ctx context.Context, email string, keyType uint32) (string, error) {
  320. key, err := svc.auth.Issue(ctx, &mainflux.IssueReq{Issuer: email, Type: keyType})
  321. if err != nil {
  322. return "", errors.Wrap(ErrUserNotFound, err)
  323. }
  324. return key.GetValue(), nil
  325. }
  326. func (svc usersService) Memberships(ctx context.Context, token, userID string, offset, limit uint64, meta Metadata) (GroupPage, error) {
  327. _, err := svc.auth.Identify(ctx, &mainflux.Token{Value: token})
  328. if err != nil {
  329. return GroupPage{}, errors.Wrap(ErrUnauthorizedAccess, err)
  330. }
  331. return svc.groups.Memberships(ctx, userID, offset, limit, meta)
  332. }