You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

email_address.go 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. // Copyright 2016 The Gogs Authors. All rights reserved.
  2. // Copyright 2020 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package user
  5. import (
  6. "context"
  7. "fmt"
  8. "net/mail"
  9. "regexp"
  10. "strings"
  11. "code.gitea.io/gitea/models/db"
  12. "code.gitea.io/gitea/modules/base"
  13. "code.gitea.io/gitea/modules/log"
  14. "code.gitea.io/gitea/modules/setting"
  15. "code.gitea.io/gitea/modules/util"
  16. "xorm.io/builder"
  17. )
  18. // ErrEmailNotActivated e-mail address has not been activated error
  19. var ErrEmailNotActivated = util.NewInvalidArgumentErrorf("e-mail address has not been activated")
  20. // ErrEmailCharIsNotSupported e-mail address contains unsupported character
  21. type ErrEmailCharIsNotSupported struct {
  22. Email string
  23. }
  24. // IsErrEmailCharIsNotSupported checks if an error is an ErrEmailCharIsNotSupported
  25. func IsErrEmailCharIsNotSupported(err error) bool {
  26. _, ok := err.(ErrEmailCharIsNotSupported)
  27. return ok
  28. }
  29. func (err ErrEmailCharIsNotSupported) Error() string {
  30. return fmt.Sprintf("e-mail address contains unsupported character [email: %s]", err.Email)
  31. }
  32. func (err ErrEmailCharIsNotSupported) Unwrap() error {
  33. return util.ErrInvalidArgument
  34. }
  35. // ErrEmailInvalid represents an error where the email address does not comply with RFC 5322
  36. // or has a leading '-' character
  37. type ErrEmailInvalid struct {
  38. Email string
  39. }
  40. // IsErrEmailInvalid checks if an error is an ErrEmailInvalid
  41. func IsErrEmailInvalid(err error) bool {
  42. _, ok := err.(ErrEmailInvalid)
  43. return ok
  44. }
  45. func (err ErrEmailInvalid) Error() string {
  46. return fmt.Sprintf("e-mail invalid [email: %s]", err.Email)
  47. }
  48. func (err ErrEmailInvalid) Unwrap() error {
  49. return util.ErrInvalidArgument
  50. }
  51. // ErrEmailAlreadyUsed represents a "EmailAlreadyUsed" kind of error.
  52. type ErrEmailAlreadyUsed struct {
  53. Email string
  54. }
  55. // IsErrEmailAlreadyUsed checks if an error is a ErrEmailAlreadyUsed.
  56. func IsErrEmailAlreadyUsed(err error) bool {
  57. _, ok := err.(ErrEmailAlreadyUsed)
  58. return ok
  59. }
  60. func (err ErrEmailAlreadyUsed) Error() string {
  61. return fmt.Sprintf("e-mail already in use [email: %s]", err.Email)
  62. }
  63. func (err ErrEmailAlreadyUsed) Unwrap() error {
  64. return util.ErrAlreadyExist
  65. }
  66. // ErrEmailAddressNotExist email address not exist
  67. type ErrEmailAddressNotExist struct {
  68. Email string
  69. }
  70. // IsErrEmailAddressNotExist checks if an error is an ErrEmailAddressNotExist
  71. func IsErrEmailAddressNotExist(err error) bool {
  72. _, ok := err.(ErrEmailAddressNotExist)
  73. return ok
  74. }
  75. func (err ErrEmailAddressNotExist) Error() string {
  76. return fmt.Sprintf("Email address does not exist [email: %s]", err.Email)
  77. }
  78. func (err ErrEmailAddressNotExist) Unwrap() error {
  79. return util.ErrNotExist
  80. }
  81. // ErrPrimaryEmailCannotDelete primary email address cannot be deleted
  82. type ErrPrimaryEmailCannotDelete struct {
  83. Email string
  84. }
  85. // IsErrPrimaryEmailCannotDelete checks if an error is an ErrPrimaryEmailCannotDelete
  86. func IsErrPrimaryEmailCannotDelete(err error) bool {
  87. _, ok := err.(ErrPrimaryEmailCannotDelete)
  88. return ok
  89. }
  90. func (err ErrPrimaryEmailCannotDelete) Error() string {
  91. return fmt.Sprintf("Primary email address cannot be deleted [email: %s]", err.Email)
  92. }
  93. func (err ErrPrimaryEmailCannotDelete) Unwrap() error {
  94. return util.ErrInvalidArgument
  95. }
  96. // EmailAddress is the list of all email addresses of a user. It also contains the
  97. // primary email address which is saved in user table.
  98. type EmailAddress struct {
  99. ID int64 `xorm:"pk autoincr"`
  100. UID int64 `xorm:"INDEX NOT NULL"`
  101. Email string `xorm:"UNIQUE NOT NULL"`
  102. LowerEmail string `xorm:"UNIQUE NOT NULL"`
  103. IsActivated bool
  104. IsPrimary bool `xorm:"DEFAULT(false) NOT NULL"`
  105. }
  106. func init() {
  107. db.RegisterModel(new(EmailAddress))
  108. }
  109. // BeforeInsert will be invoked by XORM before inserting a record
  110. func (email *EmailAddress) BeforeInsert() {
  111. if email.LowerEmail == "" {
  112. email.LowerEmail = strings.ToLower(email.Email)
  113. }
  114. }
  115. var emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
  116. // ValidateEmail check if email is a allowed address
  117. func ValidateEmail(email string) error {
  118. if len(email) == 0 {
  119. return nil
  120. }
  121. if !emailRegexp.MatchString(email) {
  122. return ErrEmailCharIsNotSupported{email}
  123. }
  124. if email[0] == '-' {
  125. return ErrEmailInvalid{email}
  126. }
  127. if _, err := mail.ParseAddress(email); err != nil {
  128. return ErrEmailInvalid{email}
  129. }
  130. // TODO: add an email allow/block list
  131. return nil
  132. }
  133. // GetEmailAddresses returns all email addresses belongs to given user.
  134. func GetEmailAddresses(uid int64) ([]*EmailAddress, error) {
  135. emails := make([]*EmailAddress, 0, 5)
  136. if err := db.GetEngine(db.DefaultContext).
  137. Where("uid=?", uid).
  138. Asc("id").
  139. Find(&emails); err != nil {
  140. return nil, err
  141. }
  142. return emails, nil
  143. }
  144. // GetEmailAddressByID gets a user's email address by ID
  145. func GetEmailAddressByID(uid, id int64) (*EmailAddress, error) {
  146. // User ID is required for security reasons
  147. email := &EmailAddress{UID: uid}
  148. if has, err := db.GetEngine(db.DefaultContext).ID(id).Get(email); err != nil {
  149. return nil, err
  150. } else if !has {
  151. return nil, nil
  152. }
  153. return email, nil
  154. }
  155. // IsEmailActive check if email is activated with a different emailID
  156. func IsEmailActive(ctx context.Context, email string, excludeEmailID int64) (bool, error) {
  157. if len(email) == 0 {
  158. return true, nil
  159. }
  160. // Can't filter by boolean field unless it's explicit
  161. cond := builder.NewCond()
  162. cond = cond.And(builder.Eq{"lower_email": strings.ToLower(email)}, builder.Neq{"id": excludeEmailID})
  163. if setting.Service.RegisterEmailConfirm {
  164. // Inactive (unvalidated) addresses don't count as active if email validation is required
  165. cond = cond.And(builder.Eq{"is_activated": true})
  166. }
  167. var em EmailAddress
  168. if has, err := db.GetEngine(ctx).Where(cond).Get(&em); has || err != nil {
  169. if has {
  170. log.Info("isEmailActive(%q, %d) found duplicate in email ID %d", email, excludeEmailID, em.ID)
  171. }
  172. return has, err
  173. }
  174. return false, nil
  175. }
  176. // IsEmailUsed returns true if the email has been used.
  177. func IsEmailUsed(ctx context.Context, email string) (bool, error) {
  178. if len(email) == 0 {
  179. return true, nil
  180. }
  181. return db.GetEngine(ctx).Where("lower_email=?", strings.ToLower(email)).Get(&EmailAddress{})
  182. }
  183. // AddEmailAddress adds an email address to given user.
  184. func AddEmailAddress(ctx context.Context, email *EmailAddress) error {
  185. email.Email = strings.TrimSpace(email.Email)
  186. used, err := IsEmailUsed(ctx, email.Email)
  187. if err != nil {
  188. return err
  189. } else if used {
  190. return ErrEmailAlreadyUsed{email.Email}
  191. }
  192. if err = ValidateEmail(email.Email); err != nil {
  193. return err
  194. }
  195. return db.Insert(ctx, email)
  196. }
  197. // AddEmailAddresses adds an email address to given user.
  198. func AddEmailAddresses(emails []*EmailAddress) error {
  199. if len(emails) == 0 {
  200. return nil
  201. }
  202. // Check if any of them has been used
  203. for i := range emails {
  204. emails[i].Email = strings.TrimSpace(emails[i].Email)
  205. used, err := IsEmailUsed(db.DefaultContext, emails[i].Email)
  206. if err != nil {
  207. return err
  208. } else if used {
  209. return ErrEmailAlreadyUsed{emails[i].Email}
  210. }
  211. if err = ValidateEmail(emails[i].Email); err != nil {
  212. return err
  213. }
  214. }
  215. if err := db.Insert(db.DefaultContext, emails); err != nil {
  216. return fmt.Errorf("Insert: %w", err)
  217. }
  218. return nil
  219. }
  220. // DeleteEmailAddress deletes an email address of given user.
  221. func DeleteEmailAddress(email *EmailAddress) (err error) {
  222. if email.IsPrimary {
  223. return ErrPrimaryEmailCannotDelete{Email: email.Email}
  224. }
  225. var deleted int64
  226. // ask to check UID
  227. address := EmailAddress{
  228. UID: email.UID,
  229. }
  230. if email.ID > 0 {
  231. deleted, err = db.GetEngine(db.DefaultContext).ID(email.ID).Delete(&address)
  232. } else {
  233. if email.Email != "" && email.LowerEmail == "" {
  234. email.LowerEmail = strings.ToLower(email.Email)
  235. }
  236. deleted, err = db.GetEngine(db.DefaultContext).
  237. Where("lower_email=?", email.LowerEmail).
  238. Delete(&address)
  239. }
  240. if err != nil {
  241. return err
  242. } else if deleted != 1 {
  243. return ErrEmailAddressNotExist{Email: email.Email}
  244. }
  245. return nil
  246. }
  247. // DeleteEmailAddresses deletes multiple email addresses
  248. func DeleteEmailAddresses(emails []*EmailAddress) (err error) {
  249. for i := range emails {
  250. if err = DeleteEmailAddress(emails[i]); err != nil {
  251. return err
  252. }
  253. }
  254. return nil
  255. }
  256. // DeleteInactiveEmailAddresses deletes inactive email addresses
  257. func DeleteInactiveEmailAddresses(ctx context.Context) error {
  258. _, err := db.GetEngine(ctx).
  259. Where("is_activated = ?", false).
  260. Delete(new(EmailAddress))
  261. return err
  262. }
  263. // ActivateEmail activates the email address to given user.
  264. func ActivateEmail(email *EmailAddress) error {
  265. ctx, committer, err := db.TxContext(db.DefaultContext)
  266. if err != nil {
  267. return err
  268. }
  269. defer committer.Close()
  270. if err := updateActivation(ctx, email, true); err != nil {
  271. return err
  272. }
  273. return committer.Commit()
  274. }
  275. func updateActivation(ctx context.Context, email *EmailAddress, activate bool) error {
  276. user, err := GetUserByID(ctx, email.UID)
  277. if err != nil {
  278. return err
  279. }
  280. if user.Rands, err = GetUserSalt(); err != nil {
  281. return err
  282. }
  283. email.IsActivated = activate
  284. if _, err := db.GetEngine(ctx).ID(email.ID).Cols("is_activated").Update(email); err != nil {
  285. return err
  286. }
  287. return UpdateUserCols(ctx, user, "rands")
  288. }
  289. // MakeEmailPrimary sets primary email address of given user.
  290. func MakeEmailPrimary(email *EmailAddress) error {
  291. has, err := db.GetEngine(db.DefaultContext).Get(email)
  292. if err != nil {
  293. return err
  294. } else if !has {
  295. return ErrEmailAddressNotExist{Email: email.Email}
  296. }
  297. if !email.IsActivated {
  298. return ErrEmailNotActivated
  299. }
  300. user := &User{}
  301. has, err = db.GetEngine(db.DefaultContext).ID(email.UID).Get(user)
  302. if err != nil {
  303. return err
  304. } else if !has {
  305. return ErrUserNotExist{
  306. UID: email.UID,
  307. Name: "",
  308. KeyID: 0,
  309. }
  310. }
  311. ctx, committer, err := db.TxContext(db.DefaultContext)
  312. if err != nil {
  313. return err
  314. }
  315. defer committer.Close()
  316. sess := db.GetEngine(ctx)
  317. // 1. Update user table
  318. user.Email = email.Email
  319. if _, err = sess.ID(user.ID).Cols("email").Update(user); err != nil {
  320. return err
  321. }
  322. // 2. Update old primary email
  323. if _, err = sess.Where("uid=? AND is_primary=?", email.UID, true).Cols("is_primary").Update(&EmailAddress{
  324. IsPrimary: false,
  325. }); err != nil {
  326. return err
  327. }
  328. // 3. update new primary email
  329. email.IsPrimary = true
  330. if _, err = sess.ID(email.ID).Cols("is_primary").Update(email); err != nil {
  331. return err
  332. }
  333. return committer.Commit()
  334. }
  335. // VerifyActiveEmailCode verifies active email code when active account
  336. func VerifyActiveEmailCode(code, email string) *EmailAddress {
  337. minutes := setting.Service.ActiveCodeLives
  338. if user := GetVerifyUser(code); user != nil {
  339. // time limit code
  340. prefix := code[:base.TimeLimitCodeLength]
  341. data := fmt.Sprintf("%d%s%s%s%s", user.ID, email, user.LowerName, user.Passwd, user.Rands)
  342. if base.VerifyTimeLimitCode(data, minutes, prefix) {
  343. emailAddress := &EmailAddress{UID: user.ID, Email: email}
  344. if has, _ := db.GetEngine(db.DefaultContext).Get(emailAddress); has {
  345. return emailAddress
  346. }
  347. }
  348. }
  349. return nil
  350. }
  351. // SearchEmailOrderBy is used to sort the results from SearchEmails()
  352. type SearchEmailOrderBy string
  353. func (s SearchEmailOrderBy) String() string {
  354. return string(s)
  355. }
  356. // Strings for sorting result
  357. const (
  358. SearchEmailOrderByEmail SearchEmailOrderBy = "email_address.lower_email ASC, email_address.is_primary DESC, email_address.id ASC"
  359. SearchEmailOrderByEmailReverse SearchEmailOrderBy = "email_address.lower_email DESC, email_address.is_primary ASC, email_address.id DESC"
  360. SearchEmailOrderByName SearchEmailOrderBy = "`user`.lower_name ASC, email_address.is_primary DESC, email_address.id ASC"
  361. SearchEmailOrderByNameReverse SearchEmailOrderBy = "`user`.lower_name DESC, email_address.is_primary ASC, email_address.id DESC"
  362. )
  363. // SearchEmailOptions are options to search e-mail addresses for the admin panel
  364. type SearchEmailOptions struct {
  365. db.ListOptions
  366. Keyword string
  367. SortType SearchEmailOrderBy
  368. IsPrimary util.OptionalBool
  369. IsActivated util.OptionalBool
  370. }
  371. // SearchEmailResult is an e-mail address found in the user or email_address table
  372. type SearchEmailResult struct {
  373. UID int64
  374. Email string
  375. IsActivated bool
  376. IsPrimary bool
  377. // From User
  378. Name string
  379. FullName string
  380. }
  381. // SearchEmails takes options i.e. keyword and part of email name to search,
  382. // it returns results in given range and number of total results.
  383. func SearchEmails(opts *SearchEmailOptions) ([]*SearchEmailResult, int64, error) {
  384. var cond builder.Cond = builder.Eq{"`user`.`type`": UserTypeIndividual}
  385. if len(opts.Keyword) > 0 {
  386. likeStr := "%" + strings.ToLower(opts.Keyword) + "%"
  387. cond = cond.And(builder.Or(
  388. builder.Like{"lower(`user`.full_name)", likeStr},
  389. builder.Like{"`user`.lower_name", likeStr},
  390. builder.Like{"email_address.lower_email", likeStr},
  391. ))
  392. }
  393. switch {
  394. case opts.IsPrimary.IsTrue():
  395. cond = cond.And(builder.Eq{"email_address.is_primary": true})
  396. case opts.IsPrimary.IsFalse():
  397. cond = cond.And(builder.Eq{"email_address.is_primary": false})
  398. }
  399. switch {
  400. case opts.IsActivated.IsTrue():
  401. cond = cond.And(builder.Eq{"email_address.is_activated": true})
  402. case opts.IsActivated.IsFalse():
  403. cond = cond.And(builder.Eq{"email_address.is_activated": false})
  404. }
  405. count, err := db.GetEngine(db.DefaultContext).Join("INNER", "`user`", "`user`.ID = email_address.uid").
  406. Where(cond).Count(new(EmailAddress))
  407. if err != nil {
  408. return nil, 0, fmt.Errorf("Count: %w", err)
  409. }
  410. orderby := opts.SortType.String()
  411. if orderby == "" {
  412. orderby = SearchEmailOrderByEmail.String()
  413. }
  414. opts.SetDefaultValues()
  415. emails := make([]*SearchEmailResult, 0, opts.PageSize)
  416. err = db.GetEngine(db.DefaultContext).Table("email_address").
  417. Select("email_address.*, `user`.name, `user`.full_name").
  418. Join("INNER", "`user`", "`user`.ID = email_address.uid").
  419. Where(cond).
  420. OrderBy(orderby).
  421. Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
  422. Find(&emails)
  423. return emails, count, err
  424. }
  425. // ActivateUserEmail will change the activated state of an email address,
  426. // either primary or secondary (all in the email_address table)
  427. func ActivateUserEmail(userID int64, email string, activate bool) (err error) {
  428. ctx, committer, err := db.TxContext(db.DefaultContext)
  429. if err != nil {
  430. return err
  431. }
  432. defer committer.Close()
  433. // Activate/deactivate a user's secondary email address
  434. // First check if there's another user active with the same address
  435. addr := EmailAddress{UID: userID, LowerEmail: strings.ToLower(email)}
  436. if has, err := db.GetByBean(ctx, &addr); err != nil {
  437. return err
  438. } else if !has {
  439. return fmt.Errorf("no such email: %d (%s)", userID, email)
  440. }
  441. if addr.IsActivated == activate {
  442. // Already in the desired state; no action
  443. return nil
  444. }
  445. if activate {
  446. if used, err := IsEmailActive(ctx, email, addr.ID); err != nil {
  447. return fmt.Errorf("unable to check isEmailActive() for %s: %w", email, err)
  448. } else if used {
  449. return ErrEmailAlreadyUsed{Email: email}
  450. }
  451. }
  452. if err = updateActivation(ctx, &addr, activate); err != nil {
  453. return fmt.Errorf("unable to updateActivation() for %d:%s: %w", addr.ID, addr.Email, err)
  454. }
  455. // Activate/deactivate a user's primary email address and account
  456. if addr.IsPrimary {
  457. user := User{ID: userID, Email: email}
  458. if has, err := db.GetByBean(ctx, &user); err != nil {
  459. return err
  460. } else if !has {
  461. return fmt.Errorf("no user with ID: %d and Email: %s", userID, email)
  462. }
  463. // The user's activation state should be synchronized with the primary email
  464. if user.IsActive != activate {
  465. user.IsActive = activate
  466. if user.Rands, err = GetUserSalt(); err != nil {
  467. return fmt.Errorf("unable to generate salt: %w", err)
  468. }
  469. if err = UpdateUserCols(ctx, &user, "is_active", "rands"); err != nil {
  470. return fmt.Errorf("unable to updateUserCols() for user ID: %d: %w", userID, err)
  471. }
  472. }
  473. }
  474. return committer.Commit()
  475. }