@@ -13,8 +13,10 @@ import (
1313 "strings"
1414
1515 "code.gitea.io/gitea/models/db"
16+ "code.gitea.io/gitea/modules/base"
1617 "code.gitea.io/gitea/modules/log"
1718 "code.gitea.io/gitea/modules/setting"
19+ "code.gitea.io/gitea/modules/util"
1820
1921 "xorm.io/builder"
2022)
@@ -275,3 +277,247 @@ func DeleteInactiveEmailAddresses(ctx context.Context) error {
275277 Delete (new (EmailAddress ))
276278 return err
277279}
280+
281+ // ActivateEmail activates the email address to given user.
282+ func ActivateEmail (email * EmailAddress ) error {
283+ ctx , committer , err := db .TxContext ()
284+ if err != nil {
285+ return err
286+ }
287+ defer committer .Close ()
288+ if err := updateActivation (db .GetEngine (ctx ), email , true ); err != nil {
289+ return err
290+ }
291+ return committer .Commit ()
292+ }
293+
294+ func updateActivation (e db.Engine , email * EmailAddress , activate bool ) error {
295+ user , err := GetUserByIDEngine (e , email .UID )
296+ if err != nil {
297+ return err
298+ }
299+ if user .Rands , err = GetUserSalt (); err != nil {
300+ return err
301+ }
302+ email .IsActivated = activate
303+ if _ , err := e .ID (email .ID ).Cols ("is_activated" ).Update (email ); err != nil {
304+ return err
305+ }
306+ return UpdateUserColsEngine (e , user , "rands" )
307+ }
308+
309+ // MakeEmailPrimary sets primary email address of given user.
310+ func MakeEmailPrimary (email * EmailAddress ) error {
311+ has , err := db .GetEngine (db .DefaultContext ).Get (email )
312+ if err != nil {
313+ return err
314+ } else if ! has {
315+ return ErrEmailAddressNotExist {Email : email .Email }
316+ }
317+
318+ if ! email .IsActivated {
319+ return ErrEmailNotActivated
320+ }
321+
322+ user := & User {}
323+ has , err = db .GetEngine (db .DefaultContext ).ID (email .UID ).Get (user )
324+ if err != nil {
325+ return err
326+ } else if ! has {
327+ return ErrUserNotExist {
328+ UID : email .UID ,
329+ Name : "" ,
330+ KeyID : 0 ,
331+ }
332+ }
333+
334+ ctx , committer , err := db .TxContext ()
335+ if err != nil {
336+ return err
337+ }
338+ defer committer .Close ()
339+ sess := db .GetEngine (ctx )
340+
341+ // 1. Update user table
342+ user .Email = email .Email
343+ if _ , err = sess .ID (user .ID ).Cols ("email" ).Update (user ); err != nil {
344+ return err
345+ }
346+
347+ // 2. Update old primary email
348+ if _ , err = sess .Where ("uid=? AND is_primary=?" , email .UID , true ).Cols ("is_primary" ).Update (& EmailAddress {
349+ IsPrimary : false ,
350+ }); err != nil {
351+ return err
352+ }
353+
354+ // 3. update new primary email
355+ email .IsPrimary = true
356+ if _ , err = sess .ID (email .ID ).Cols ("is_primary" ).Update (email ); err != nil {
357+ return err
358+ }
359+
360+ return committer .Commit ()
361+ }
362+
363+ // VerifyActiveEmailCode verifies active email code when active account
364+ func VerifyActiveEmailCode (code , email string ) * EmailAddress {
365+ minutes := setting .Service .ActiveCodeLives
366+
367+ if user := GetVerifyUser (code ); user != nil {
368+ // time limit code
369+ prefix := code [:base .TimeLimitCodeLength ]
370+ data := fmt .Sprintf ("%d%s%s%s%s" , user .ID , email , user .LowerName , user .Passwd , user .Rands )
371+
372+ if base .VerifyTimeLimitCode (data , minutes , prefix ) {
373+ emailAddress := & EmailAddress {UID : user .ID , Email : email }
374+ if has , _ := db .GetEngine (db .DefaultContext ).Get (emailAddress ); has {
375+ return emailAddress
376+ }
377+ }
378+ }
379+ return nil
380+ }
381+
382+ // SearchEmailOrderBy is used to sort the results from SearchEmails()
383+ type SearchEmailOrderBy string
384+
385+ func (s SearchEmailOrderBy ) String () string {
386+ return string (s )
387+ }
388+
389+ // Strings for sorting result
390+ const (
391+ SearchEmailOrderByEmail SearchEmailOrderBy = "email_address.lower_email ASC, email_address.is_primary DESC, email_address.id ASC"
392+ SearchEmailOrderByEmailReverse SearchEmailOrderBy = "email_address.lower_email DESC, email_address.is_primary ASC, email_address.id DESC"
393+ SearchEmailOrderByName SearchEmailOrderBy = "`user`.lower_name ASC, email_address.is_primary DESC, email_address.id ASC"
394+ SearchEmailOrderByNameReverse SearchEmailOrderBy = "`user`.lower_name DESC, email_address.is_primary ASC, email_address.id DESC"
395+ )
396+
397+ // SearchEmailOptions are options to search e-mail addresses for the admin panel
398+ type SearchEmailOptions struct {
399+ db.ListOptions
400+ Keyword string
401+ SortType SearchEmailOrderBy
402+ IsPrimary util.OptionalBool
403+ IsActivated util.OptionalBool
404+ }
405+
406+ // SearchEmailResult is an e-mail address found in the user or email_address table
407+ type SearchEmailResult struct {
408+ UID int64
409+ Email string
410+ IsActivated bool
411+ IsPrimary bool
412+ // From User
413+ Name string
414+ FullName string
415+ }
416+
417+ // SearchEmails takes options i.e. keyword and part of email name to search,
418+ // it returns results in given range and number of total results.
419+ func SearchEmails (opts * SearchEmailOptions ) ([]* SearchEmailResult , int64 , error ) {
420+ var cond builder.Cond = builder.Eq {"`user`.`type`" : UserTypeIndividual }
421+ if len (opts .Keyword ) > 0 {
422+ likeStr := "%" + strings .ToLower (opts .Keyword ) + "%"
423+ cond = cond .And (builder .Or (
424+ builder.Like {"lower(`user`.full_name)" , likeStr },
425+ builder.Like {"`user`.lower_name" , likeStr },
426+ builder.Like {"email_address.lower_email" , likeStr },
427+ ))
428+ }
429+
430+ switch {
431+ case opts .IsPrimary .IsTrue ():
432+ cond = cond .And (builder.Eq {"email_address.is_primary" : true })
433+ case opts .IsPrimary .IsFalse ():
434+ cond = cond .And (builder.Eq {"email_address.is_primary" : false })
435+ }
436+
437+ switch {
438+ case opts .IsActivated .IsTrue ():
439+ cond = cond .And (builder.Eq {"email_address.is_activated" : true })
440+ case opts .IsActivated .IsFalse ():
441+ cond = cond .And (builder.Eq {"email_address.is_activated" : false })
442+ }
443+
444+ count , err := db .GetEngine (db .DefaultContext ).Join ("INNER" , "`user`" , "`user`.ID = email_address.uid" ).
445+ Where (cond ).Count (new (EmailAddress ))
446+ if err != nil {
447+ return nil , 0 , fmt .Errorf ("Count: %v" , err )
448+ }
449+
450+ orderby := opts .SortType .String ()
451+ if orderby == "" {
452+ orderby = SearchEmailOrderByEmail .String ()
453+ }
454+
455+ opts .SetDefaultValues ()
456+
457+ emails := make ([]* SearchEmailResult , 0 , opts .PageSize )
458+ err = db .GetEngine (db .DefaultContext ).Table ("email_address" ).
459+ Select ("email_address.*, `user`.name, `user`.full_name" ).
460+ Join ("INNER" , "`user`" , "`user`.ID = email_address.uid" ).
461+ Where (cond ).
462+ OrderBy (orderby ).
463+ Limit (opts .PageSize , (opts .Page - 1 )* opts .PageSize ).
464+ Find (& emails )
465+
466+ return emails , count , err
467+ }
468+
469+ // ActivateUserEmail will change the activated state of an email address,
470+ // either primary or secondary (all in the email_address table)
471+ func ActivateUserEmail (userID int64 , email string , activate bool ) (err error ) {
472+ ctx , committer , err := db .TxContext ()
473+ if err != nil {
474+ return err
475+ }
476+ defer committer .Close ()
477+ sess := db .GetEngine (ctx )
478+
479+ // Activate/deactivate a user's secondary email address
480+ // First check if there's another user active with the same address
481+ addr := EmailAddress {UID : userID , LowerEmail : strings .ToLower (email )}
482+ if has , err := sess .Get (& addr ); err != nil {
483+ return err
484+ } else if ! has {
485+ return fmt .Errorf ("no such email: %d (%s)" , userID , email )
486+ }
487+ if addr .IsActivated == activate {
488+ // Already in the desired state; no action
489+ return nil
490+ }
491+ if activate {
492+ if used , err := IsEmailActive (ctx , email , addr .ID ); err != nil {
493+ return fmt .Errorf ("unable to check isEmailActive() for %s: %v" , email , err )
494+ } else if used {
495+ return ErrEmailAlreadyUsed {Email : email }
496+ }
497+ }
498+ if err = updateActivation (sess , & addr , activate ); err != nil {
499+ return fmt .Errorf ("unable to updateActivation() for %d:%s: %w" , addr .ID , addr .Email , err )
500+ }
501+
502+ // Activate/deactivate a user's primary email address and account
503+ if addr .IsPrimary {
504+ user := User {ID : userID , Email : email }
505+ if has , err := sess .Get (& user ); err != nil {
506+ return err
507+ } else if ! has {
508+ return fmt .Errorf ("no user with ID: %d and Email: %s" , userID , email )
509+ }
510+ // The user's activation state should be synchronized with the primary email
511+ if user .IsActive != activate {
512+ user .IsActive = activate
513+ if user .Rands , err = GetUserSalt (); err != nil {
514+ return fmt .Errorf ("unable to generate salt: %v" , err )
515+ }
516+ if err = UpdateUserColsEngine (sess , & user , "is_active" , "rands" ); err != nil {
517+ return fmt .Errorf ("unable to updateUserCols() for user ID: %d: %v" , userID , err )
518+ }
519+ }
520+ }
521+
522+ return committer .Commit ()
523+ }
0 commit comments