diff --git a/Taskfile.yml b/Taskfile.yml index 431421b40..6607b8a60 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -5,6 +5,8 @@ vars: sh: echo '{{if eq OS "windows"}}.exe{{end}}' tasks: + default: + deps: [build] web: desc: Build the binary and start the web server deps: [build] diff --git a/internal/cmd/web.go b/internal/cmd/web.go index 42e4e0650..1689afefd 100644 --- a/internal/cmd/web.go +++ b/internal/cmd/web.go @@ -99,7 +99,7 @@ func newMacaron() *macaron.Macaron { conf.Picture.AvatarUploadPath, macaron.StaticOptions{ ETag: true, - Prefix: db.USER_AVATAR_URL_PREFIX, + Prefix: conf.UsersAvatarURLPath, SkipLogging: conf.Server.DisableRouterLog, }, )) diff --git a/internal/conf/mocks.go b/internal/conf/mocks.go index d4e57d385..af5b1bd70 100644 --- a/internal/conf/mocks.go +++ b/internal/conf/mocks.go @@ -16,6 +16,14 @@ func SetMockApp(t *testing.T, opts AppOpts) { }) } +func SetMockAuth(t *testing.T, otps AuthOpts) { + before := Auth + Auth = otps + t.Cleanup(func() { + Auth = before + }) +} + func SetMockServer(t *testing.T, opts ServerOpts) { before := Server Server = opts diff --git a/internal/conf/static.go b/internal/conf/static.go index b0092dde8..8132547fc 100644 --- a/internal/conf/static.go +++ b/internal/conf/static.go @@ -12,6 +12,12 @@ import ( "github.com/gogs/go-libravatar" ) +const ( + // UsersAvatarURLPath is used to identify whether a URL is to access user + // avatars. + UsersAvatarURLPath = "avatars" +) + // ℹ️ README: This file contains static values that should only be set at initialization time. // // ⚠️ WARNING: After changing any options, do not forget to update template of @@ -71,20 +77,6 @@ var ( FromEmail string `ini:"-"` // Parsed email address of From without person's name. } - // Authentication settings - Auth struct { - ActivateCodeLives int - ResetPasswordCodeLives int - RequireEmailConfirmation bool - RequireSigninView bool - DisableRegistration bool - EnableRegistrationCaptcha bool - - EnableReverseProxyAuthentication bool - EnableReverseProxyAutoRegistration bool - ReverseProxyAuthenticationHeader string - } - // User settings User struct { EnableEmailNotification bool @@ -268,6 +260,22 @@ type AppOpts struct { // Application settings var App AppOpts +type AuthOpts struct { + ActivateCodeLives int + ResetPasswordCodeLives int + RequireEmailConfirmation bool + RequireSigninView bool + DisableRegistration bool + EnableRegistrationCaptcha bool + + EnableReverseProxyAuthentication bool + EnableReverseProxyAutoRegistration bool + ReverseProxyAuthenticationHeader string +} + +// Authentication settings +var Auth AuthOpts + type ServerOpts struct { ExternalURL string `ini:"EXTERNAL_URL"` Domain string diff --git a/internal/db/issue_mail.go b/internal/db/issue_mail.go index c9b5471a9..91d89a415 100644 --- a/internal/db/issue_mail.go +++ b/internal/db/issue_mail.go @@ -13,6 +13,7 @@ import ( "gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/email" "gogs.io/gogs/internal/markup" + "gogs.io/gogs/internal/userutil" ) func (issue *Issue) MailSubject() string { @@ -36,12 +37,14 @@ func (this mailerUser) Email() string { return this.user.Email } -func (this mailerUser) GenerateActivateCode() string { - return this.user.GenerateActivateCode() -} - func (this mailerUser) GenerateEmailActivateCode(email string) string { - return this.user.GenerateEmailActivateCode(email) + return userutil.GenerateActivateCode( + this.user.ID, + email, + this.user.Name, + this.user.Passwd, + this.user.Rands, + ) } func NewMailerUser(u *User) email.User { diff --git a/internal/db/org.go b/internal/db/org.go index cf984f148..c5c55f444 100644 --- a/internal/db/org.go +++ b/internal/db/org.go @@ -177,7 +177,7 @@ func GetOrgByName(name string) (*User, error) { } u := &User{ LowerName: strings.ToLower(name), - Type: UserOrganization, + Type: UserTypeOrganization, } has, err := x.Get(u) if err != nil { diff --git a/internal/db/repo.go b/internal/db/repo.go index 94f50dfee..972280150 100644 --- a/internal/db/repo.go +++ b/internal/db/repo.go @@ -1157,8 +1157,8 @@ func (err ErrReachLimitOfRepo) Error() string { // CreateRepository creates a repository for given user or organization. func CreateRepository(doer, owner *User, opts CreateRepoOptionsLegacy) (_ *Repository, err error) { - if !owner.CanCreateRepo() { - return nil, ErrReachLimitOfRepo{Limit: owner.RepoCreationNum()} + if !owner.canCreateRepo() { + return nil, ErrReachLimitOfRepo{Limit: owner.maxNumRepos()} } repo := &Repository{ @@ -2467,8 +2467,8 @@ func HasForkedRepo(ownerID, repoID int64) (*Repository, bool, error) { // ForkRepository creates a fork of target repository under another user domain. func ForkRepository(doer, owner *User, baseRepo *Repository, name, desc string) (_ *Repository, err error) { - if !owner.CanCreateRepo() { - return nil, ErrReachLimitOfRepo{Limit: owner.RepoCreationNum()} + if !owner.canCreateRepo() { + return nil, ErrReachLimitOfRepo{Limit: owner.maxNumRepos()} } repo := &Repository{ diff --git a/internal/db/user.go b/internal/db/user.go index 08b7e09eb..33b69af16 100644 --- a/internal/db/user.go +++ b/internal/db/user.go @@ -27,7 +27,6 @@ import ( "xorm.io/xorm" "github.com/gogs/git-module" - api "github.com/gogs/go-gogs-client" "gogs.io/gogs/internal/avatar" "gogs.io/gogs/internal/conf" @@ -37,77 +36,13 @@ import ( "gogs.io/gogs/internal/tool" ) -// USER_AVATAR_URL_PREFIX is used to identify a URL is to access user avatar. -const USER_AVATAR_URL_PREFIX = "avatars" - -type UserType int - -const ( - UserIndividual UserType = iota // Historic reason to make it starts at 0. - UserOrganization -) - -// User represents the object of individual and member of organization. -type User struct { - ID int64 `gorm:"primaryKey"` - LowerName string `xorm:"UNIQUE NOT NULL" gorm:"unique;not null"` - Name string `xorm:"UNIQUE NOT NULL" gorm:"not null"` - FullName string - // Email is the primary email address (to be used for communication) - Email string `xorm:"NOT NULL" gorm:"not null"` - Passwd string `xorm:"NOT NULL" gorm:"not null"` - LoginSource int64 `xorm:"NOT NULL DEFAULT 0" gorm:"not null;default:0"` - LoginName string - Type UserType - OwnedOrgs []*User `xorm:"-" gorm:"-" json:"-"` - Orgs []*User `xorm:"-" gorm:"-" json:"-"` - Repos []*Repository `xorm:"-" gorm:"-" json:"-"` - Location string - Website string - Rands string `xorm:"VARCHAR(10)" gorm:"type:VARCHAR(10)"` - Salt string `xorm:"VARCHAR(10)" gorm:"type:VARCHAR(10)"` - - Created time.Time `xorm:"-" gorm:"-" json:"-"` - CreatedUnix int64 - Updated time.Time `xorm:"-" gorm:"-" json:"-"` - UpdatedUnix int64 - - // Remember visibility choice for convenience, true for private - LastRepoVisibility bool - // Maximum repository creation limit, -1 means use global default - MaxRepoCreation int `xorm:"NOT NULL DEFAULT -1" gorm:"not null;default:-1"` - - // Permissions - IsActive bool // Activate primary email - IsAdmin bool - AllowGitHook bool - AllowImportLocal bool // Allow migrate repository by local path - ProhibitLogin bool - - // Avatar - Avatar string `xorm:"VARCHAR(2048) NOT NULL" gorm:"type:VARCHAR(2048);not null"` - AvatarEmail string `xorm:"NOT NULL" gorm:"not null"` - UseCustomAvatar bool - - // Counters - NumFollowers int - NumFollowing int `xorm:"NOT NULL DEFAULT 0" gorm:"not null;default:0"` - NumStars int - NumRepos int - - // For organization - Description string - NumTeams int - NumMembers int - Teams []*Team `xorm:"-" gorm:"-" json:"-"` - Members []*User `xorm:"-" gorm:"-" json:"-"` -} - +// TODO(unknwon): Delete me once refactoring is done. func (u *User) BeforeInsert() { u.CreatedUnix = time.Now().Unix() u.UpdatedUnix = u.CreatedUnix } +// TODO(unknwon): Refactoring together with methods that do updates. func (u *User) BeforeUpdate() { if u.MaxRepoCreation < -1 { u.MaxRepoCreation = -1 @@ -115,6 +50,7 @@ func (u *User) BeforeUpdate() { u.UpdatedUnix = time.Now().Unix() } +// TODO(unknwon): Delete me once refactoring is done. func (u *User) AfterSet(colName string, _ xorm.Cell) { switch colName { case "created_unix": @@ -124,81 +60,6 @@ func (u *User) AfterSet(colName string, _ xorm.Cell) { } } -func (u *User) APIFormat() *api.User { - return &api.User{ - ID: u.ID, - UserName: u.Name, - Login: u.Name, - FullName: u.FullName, - Email: u.Email, - AvatarUrl: u.AvatarLink(), - } -} - -func (u *User) RepoCreationNum() int { - if u.MaxRepoCreation <= -1 { - return conf.Repository.MaxCreationLimit - } - return u.MaxRepoCreation -} - -func (u *User) CanCreateRepo() bool { - if u.MaxRepoCreation <= -1 { - if conf.Repository.MaxCreationLimit <= -1 { - return true - } - return u.NumRepos < conf.Repository.MaxCreationLimit - } - return u.NumRepos < u.MaxRepoCreation -} - -func (u *User) CanCreateOrganization() bool { - return !conf.Admin.DisableRegularOrgCreation || u.IsAdmin -} - -// CanEditGitHook returns true if user can edit Git hooks. -func (u *User) CanEditGitHook() bool { - return u.IsAdmin || u.AllowGitHook -} - -// CanImportLocal returns true if user can migrate repository by local path. -func (u *User) CanImportLocal() bool { - return conf.Repository.EnableLocalPathMigration && (u.IsAdmin || u.AllowImportLocal) -} - -// DashboardLink returns the user dashboard page link. -func (u *User) DashboardLink() string { - if u.IsOrganization() { - return conf.Server.Subpath + "/org/" + u.Name + "/dashboard/" - } - return conf.Server.Subpath + "/" -} - -// HomeLink returns the user or organization home page link. -func (u *User) HomeLink() string { - return conf.Server.Subpath + "/" + u.Name -} - -func (u *User) HTMLURL() string { - return conf.Server.ExternalURL + u.Name -} - -// GenerateEmailActivateCode generates an activate code based on user information and given e-mail. -func (u *User) GenerateEmailActivateCode(email string) string { - code := tool.CreateTimeLimitCode( - com.ToStr(u.ID)+email+u.LowerName+u.Passwd+u.Rands, - conf.Auth.ActivateCodeLives, nil) - - // Add tail hex username - code += hex.EncodeToString([]byte(u.LowerName)) - return code -} - -// GenerateActivateCode generates an activate code based on user information. -func (u *User) GenerateActivateCode() string { - return u.GenerateEmailActivateCode(u.Email) -} - // CustomAvatarPath returns user custom avatar file path. func (u *User) CustomAvatarPath() string { return filepath.Join(conf.Picture.AvatarUploadPath, com.ToStr(u.ID)) @@ -246,7 +107,7 @@ func (u *User) RelAvatarLink() string { if !com.IsExist(u.CustomAvatarPath()) { return defaultImgUrl } - return fmt.Sprintf("%s/%s/%d", conf.Server.Subpath, USER_AVATAR_URL_PREFIX, u.ID) + return fmt.Sprintf("%s/%s/%d", conf.Server.Subpath, conf.UsersAvatarURLPath, u.ID) case conf.Picture.DisableGravatar: if !com.IsExist(u.CustomAvatarPath()) { if err := u.GenerateRandomAvatar(); err != nil { @@ -254,7 +115,7 @@ func (u *User) RelAvatarLink() string { } } - return fmt.Sprintf("%s/%s/%d", conf.Server.Subpath, USER_AVATAR_URL_PREFIX, u.ID) + return fmt.Sprintf("%s/%s/%d", conf.Server.Subpath, conf.UsersAvatarURLPath, u.ID) } return tool.AvatarLink(u.AvatarEmail) } @@ -374,7 +235,7 @@ func (u *User) IsWriterOfRepo(repo *Repository) bool { // IsOrganization returns true if user is actually a organization. func (u *User) IsOrganization() bool { - return u.Type == UserOrganization + return u.Type == UserTypeOrganization } // IsUserOrgOwner returns true if user is in the owner team of given organization. @@ -434,7 +295,7 @@ func (u *User) GetOrganizations(showPrivate bool) error { } u.Orgs = make([]*User, 0, len(orgIDs)) - if err = x.Where("type = ?", UserOrganization).In("id", orgIDs).Find(&u.Orgs); err != nil { + if err = x.Where("type = ?", UserTypeOrganization).In("id", orgIDs).Find(&u.Orgs); err != nil { return err } return nil diff --git a/internal/db/user_mail.go b/internal/db/user_mail.go index fc6356fe0..ea6c1a739 100644 --- a/internal/db/user_mail.go +++ b/internal/db/user_mail.go @@ -69,7 +69,7 @@ func isEmailUsed(e Engine, email string) (bool, error) { } // We need to check primary email of users as well. - return e.Where("type=?", UserIndividual).And("email=?", email).Get(new(User)) + return e.Where("type=?", UserTypeIndividual).And("email=?", email).Get(new(User)) } // IsEmailUsed returns true if the email has been used. diff --git a/internal/db/users.go b/internal/db/users.go index d52cfc4c8..94bc25f5f 100644 --- a/internal/db/users.go +++ b/internal/db/users.go @@ -11,10 +11,12 @@ import ( "time" "github.com/go-macaron/binding" + api "github.com/gogs/go-gogs-client" "github.com/pkg/errors" "gorm.io/gorm" "gogs.io/gogs/internal/auth" + "gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/cryptoutil" "gogs.io/gogs/internal/errutil" ) @@ -54,27 +56,6 @@ type UsersStore interface { var Users UsersStore -// BeforeCreate implements the GORM create hook. -func (u *User) BeforeCreate(tx *gorm.DB) error { - if u.CreatedUnix == 0 { - u.CreatedUnix = tx.NowFunc().Unix() - u.UpdatedUnix = u.CreatedUnix - } - return nil -} - -// AfterFind implements the GORM query hook. -func (u *User) AfterFind(_ *gorm.DB) error { - u.Created = time.Unix(u.CreatedUnix, 0).Local() - u.Updated = time.Unix(u.UpdatedUnix, 0).Local() - return nil -} - -// IsLocal returns true if user is created as local account. -func (u *User) IsLocal() bool { - return u.LoginSource <= 0 -} - var _ UsersStore = (*users)(nil) type users struct { @@ -303,7 +284,7 @@ func (db *users) GetByEmail(ctx context.Context, email string) (*User, error) { // First try to find the user by primary email user := new(User) err := db.WithContext(ctx). - Where("email = ? AND type = ? AND is_active = ?", email, UserIndividual, true). + Where("email = ? AND type = ? AND is_active = ?", email, UserTypeIndividual, true). First(user). Error if err == nil { @@ -357,3 +338,147 @@ func (db *users) HasForkedRepository(ctx context.Context, userID, repoID int64) db.WithContext(ctx).Model(new(Repository)).Where("owner_id = ? AND fork_id = ?", userID, repoID).Count(&count) return count > 0 } + +// UserType indicates the type of the user account. +type UserType int + +const ( + UserTypeIndividual UserType = iota // NOTE: Historic reason to make it starts at 0. + UserTypeOrganization +) + +// User represents the object of an individual or an organization. +type User struct { + ID int64 `gorm:"primaryKey"` + LowerName string `xorm:"UNIQUE NOT NULL" gorm:"unique;not null"` + Name string `xorm:"UNIQUE NOT NULL" gorm:"not null"` + FullName string + // Email is the primary email address (to be used for communication) + Email string `xorm:"NOT NULL" gorm:"not null"` + Passwd string `xorm:"NOT NULL" gorm:"not null"` + LoginSource int64 `xorm:"NOT NULL DEFAULT 0" gorm:"not null;default:0"` + LoginName string + Type UserType + OwnedOrgs []*User `xorm:"-" gorm:"-" json:"-"` + Orgs []*User `xorm:"-" gorm:"-" json:"-"` + Repos []*Repository `xorm:"-" gorm:"-" json:"-"` + Location string + Website string + Rands string `xorm:"VARCHAR(10)" gorm:"type:VARCHAR(10)"` + Salt string `xorm:"VARCHAR(10)" gorm:"type:VARCHAR(10)"` + + Created time.Time `xorm:"-" gorm:"-" json:"-"` + CreatedUnix int64 + Updated time.Time `xorm:"-" gorm:"-" json:"-"` + UpdatedUnix int64 + + // Remember visibility choice for convenience, true for private + LastRepoVisibility bool + // Maximum repository creation limit, -1 means use global default + MaxRepoCreation int `xorm:"NOT NULL DEFAULT -1" gorm:"not null;default:-1"` + + // Permissions + IsActive bool // Activate primary email + IsAdmin bool + AllowGitHook bool + AllowImportLocal bool // Allow migrate repository by local path + ProhibitLogin bool + + // Avatar + Avatar string `xorm:"VARCHAR(2048) NOT NULL" gorm:"type:VARCHAR(2048);not null"` + AvatarEmail string `xorm:"NOT NULL" gorm:"not null"` + UseCustomAvatar bool + + // Counters + NumFollowers int + NumFollowing int `xorm:"NOT NULL DEFAULT 0" gorm:"not null;default:0"` + NumStars int + NumRepos int + + // For organization + Description string + NumTeams int + NumMembers int + Teams []*Team `xorm:"-" gorm:"-" json:"-"` + Members []*User `xorm:"-" gorm:"-" json:"-"` +} + +// BeforeCreate implements the GORM create hook. +func (u *User) BeforeCreate(tx *gorm.DB) error { + if u.CreatedUnix == 0 { + u.CreatedUnix = tx.NowFunc().Unix() + u.UpdatedUnix = u.CreatedUnix + } + return nil +} + +// AfterFind implements the GORM query hook. +func (u *User) AfterFind(_ *gorm.DB) error { + u.Created = time.Unix(u.CreatedUnix, 0).Local() + u.Updated = time.Unix(u.UpdatedUnix, 0).Local() + return nil +} + +// IsLocal returns true if user is created as local account. +func (u *User) IsLocal() bool { + return u.LoginSource <= 0 +} + +// APIFormat returns the API format of a user. +func (u *User) APIFormat() *api.User { + return &api.User{ + ID: u.ID, + UserName: u.Name, + Login: u.Name, + FullName: u.FullName, + Email: u.Email, + AvatarUrl: u.AvatarLink(), + } +} + +// maxNumRepos returns the maximum number of repositories that the user can have +// direct ownership. +func (u *User) maxNumRepos() int { + if u.MaxRepoCreation <= -1 { + return conf.Repository.MaxCreationLimit + } + return u.MaxRepoCreation +} + +// canCreateRepo returns true if the user can create a repository. +func (u *User) canCreateRepo() bool { + return u.maxNumRepos() <= -1 || u.NumRepos < u.maxNumRepos() +} + +// CanCreateOrganization returns true if user can create organizations. +func (u *User) CanCreateOrganization() bool { + return !conf.Admin.DisableRegularOrgCreation || u.IsAdmin +} + +// CanEditGitHook returns true if user can edit Git hooks. +func (u *User) CanEditGitHook() bool { + return u.IsAdmin || u.AllowGitHook +} + +// CanImportLocal returns true if user can migrate repositories by local path. +func (u *User) CanImportLocal() bool { + return conf.Repository.EnableLocalPathMigration && (u.IsAdmin || u.AllowImportLocal) +} + +// HomeURLPath returns the URL path to the user or organization home page. +// +// TODO(unknwon): This is also used in templates, which should be fixed by +// having a dedicated type `template.User` and move this to the "userutil" +// package. +func (u *User) HomeURLPath() string { + return conf.Server.Subpath + "/" + u.Name +} + +// HTMLURL returns the HTML URL to the user or organization home page. +// +// TODO(unknwon): This is also used in templates, which should be fixed by +// having a dedicated type `template.User` and move this to the "userutil" +// package. +func (u *User) HTMLURL() string { + return conf.Server.ExternalURL + u.Name +} diff --git a/internal/db/users_test.go b/internal/db/users_test.go index 68c1bfdcc..67be21dd5 100644 --- a/internal/db/users_test.go +++ b/internal/db/users_test.go @@ -198,7 +198,7 @@ func usersGetByEmail(t *testing.T, db *users) { org, err := db.Create(ctx, "gogs", "gogs@exmaple.com", CreateUserOptions{}) require.NoError(t, err) - err = db.Model(&User{}).Where("id", org.ID).UpdateColumn("type", UserOrganization).Error + err = db.Model(&User{}).Where("id", org.ID).UpdateColumn("type", UserTypeOrganization).Error require.NoError(t, err) _, err = db.GetByEmail(ctx, org.Email) diff --git a/internal/email/email.go b/internal/email/email.go index f63e3c863..107b5e11c 100644 --- a/internal/email/email.go +++ b/internal/email/email.go @@ -86,7 +86,6 @@ type User interface { ID() int64 DisplayName() string Email() string - GenerateActivateCode() string GenerateEmailActivateCode(string) string } @@ -122,11 +121,11 @@ func SendUserMail(_ *macaron.Context, u User, tpl, code, subject, info string) { } func SendActivateAccountMail(c *macaron.Context, u User) { - SendUserMail(c, u, MAIL_AUTH_ACTIVATE, u.GenerateActivateCode(), c.Tr("mail.activate_account"), "activate account") + SendUserMail(c, u, MAIL_AUTH_ACTIVATE, u.GenerateEmailActivateCode(u.Email()), c.Tr("mail.activate_account"), "activate account") } func SendResetPasswordMail(c *macaron.Context, u User) { - SendUserMail(c, u, MAIL_AUTH_RESET_PASSWORD, u.GenerateActivateCode(), c.Tr("mail.reset_password"), "reset password") + SendUserMail(c, u, MAIL_AUTH_RESET_PASSWORD, u.GenerateEmailActivateCode(u.Email()), c.Tr("mail.reset_password"), "reset password") } // SendActivateAccountMail sends confirmation email. diff --git a/internal/route/admin/orgs.go b/internal/route/admin/orgs.go index fd0340279..06aa74be0 100644 --- a/internal/route/admin/orgs.go +++ b/internal/route/admin/orgs.go @@ -21,7 +21,7 @@ func Organizations(c *context.Context) { c.Data["PageIsAdminOrganizations"] = true route.RenderUserSearch(c, &route.UserSearchOptions{ - Type: db.UserOrganization, + Type: db.UserTypeOrganization, Counter: db.CountOrganizations, Ranger: db.Organizations, PageSize: conf.UI.Admin.OrgPagingNum, diff --git a/internal/route/admin/users.go b/internal/route/admin/users.go index e4e0a6d69..ef890e7db 100644 --- a/internal/route/admin/users.go +++ b/internal/route/admin/users.go @@ -30,7 +30,7 @@ func Users(c *context.Context) { c.Data["PageIsAdminUsers"] = true route.RenderUserSearch(c, &route.UserSearchOptions{ - Type: db.UserIndividual, + Type: db.UserTypeIndividual, Counter: db.CountUsers, Ranger: db.ListUsers, PageSize: conf.UI.Admin.UserPagingNum, diff --git a/internal/route/api/v1/org/org.go b/internal/route/api/v1/org/org.go index 7a7de50e1..4d987dbfc 100644 --- a/internal/route/api/v1/org/org.go +++ b/internal/route/api/v1/org/org.go @@ -27,7 +27,7 @@ func CreateOrgForUser(c *context.APIContext, apiForm api.CreateOrgOption, user * Website: apiForm.Website, Location: apiForm.Location, IsActive: true, - Type: db.UserOrganization, + Type: db.UserTypeOrganization, } if err := db.CreateOrganization(org, user); err != nil { if db.IsErrUserAlreadyExist(err) || diff --git a/internal/route/api/v1/user/user.go b/internal/route/api/v1/user/user.go index c57f8fed0..2b26a2827 100644 --- a/internal/route/api/v1/user/user.go +++ b/internal/route/api/v1/user/user.go @@ -19,7 +19,7 @@ import ( func Search(c *context.APIContext) { opts := &db.SearchUserOptions{ Keyword: c.Query("q"), - Type: db.UserIndividual, + Type: db.UserTypeIndividual, PageSize: com.StrTo(c.Query("limit")).MustInt(), } if opts.PageSize == 0 { diff --git a/internal/route/home.go b/internal/route/home.go index b1e2cc7a0..51c7b54b5 100644 --- a/internal/route/home.go +++ b/internal/route/home.go @@ -138,7 +138,7 @@ func ExploreUsers(c *context.Context) { c.Data["PageIsExploreUsers"] = true RenderUserSearch(c, &UserSearchOptions{ - Type: db.UserIndividual, + Type: db.UserTypeIndividual, Counter: db.CountUsers, Ranger: db.ListUsers, PageSize: conf.UI.ExplorePagingNum, @@ -153,7 +153,7 @@ func ExploreOrganizations(c *context.Context) { c.Data["PageIsExploreOrganizations"] = true RenderUserSearch(c, &UserSearchOptions{ - Type: db.UserOrganization, + Type: db.UserTypeOrganization, Counter: db.CountOrganizations, Ranger: db.Organizations, PageSize: conf.UI.ExplorePagingNum, diff --git a/internal/route/org/org.go b/internal/route/org/org.go index 424cbd153..badd0d81d 100644 --- a/internal/route/org/org.go +++ b/internal/route/org/org.go @@ -32,7 +32,7 @@ func CreatePost(c *context.Context, f form.CreateOrg) { org := &db.User{ Name: f.OrgName, IsActive: true, - Type: db.UserOrganization, + Type: db.UserTypeOrganization, } if err := db.CreateOrganization(org, c.User); err != nil { diff --git a/internal/route/repo/pull.go b/internal/route/repo/pull.go index 68389532f..d65b1e105 100644 --- a/internal/route/repo/pull.go +++ b/internal/route/repo/pull.go @@ -135,7 +135,7 @@ func ForkPost(c *context.Context, f form.CreateRepo) { c.Data["Err_RepoName"] = true switch { case db.IsErrReachLimitOfRepo(err): - c.RenderWithErr(c.Tr("repo.form.reach_limit_of_creation", c.User.RepoCreationNum()), FORK, &f) + c.RenderWithErr(c.Tr("repo.form.reach_limit_of_creation", err.(db.ErrReachLimitOfRepo).Limit), FORK, &f) case db.IsErrRepoAlreadyExist(err): c.RenderWithErr(c.Tr("repo.settings.new_owner_has_same_repo"), FORK, &f) case db.IsErrNameNotAllowed(err): diff --git a/internal/route/repo/repo.go b/internal/route/repo/repo.go index c1fb327ba..943540c83 100644 --- a/internal/route/repo/repo.go +++ b/internal/route/repo/repo.go @@ -86,10 +86,10 @@ func Create(c *context.Context) { c.Success(CREATE) } -func handleCreateError(c *context.Context, owner *db.User, err error, name, tpl string, form interface{}) { +func handleCreateError(c *context.Context, err error, name, tpl string, form interface{}) { switch { case db.IsErrReachLimitOfRepo(err): - c.RenderWithErr(c.Tr("repo.form.reach_limit_of_creation", owner.RepoCreationNum()), tpl, form) + c.RenderWithErr(c.Tr("repo.form.reach_limit_of_creation", err.(db.ErrReachLimitOfRepo).Limit), tpl, form) case db.IsErrRepoAlreadyExist(err): c.Data["Err_RepoName"] = true c.RenderWithErr(c.Tr("form.repo_name_been_taken"), tpl, form) @@ -141,7 +141,7 @@ func CreatePost(c *context.Context, f form.CreateRepo) { } } - handleCreateError(c, ctxUser, err, "CreatePost", CREATE, &f) + handleCreateError(c, err, "CreatePost", CREATE, &f) } func Migrate(c *context.Context) { @@ -227,7 +227,7 @@ func MigratePost(c *context.Context, f form.MigrateRepo) { return } - handleCreateError(c, ctxUser, err, "MigratePost", MIGRATE, &f) + handleCreateError(c, err, "MigratePost", MIGRATE, &f) } func Action(c *context.Context) { diff --git a/internal/route/repo/setting.go b/internal/route/repo/setting.go index 8c706981b..a977fcc89 100644 --- a/internal/route/repo/setting.go +++ b/internal/route/repo/setting.go @@ -22,6 +22,7 @@ import ( "gogs.io/gogs/internal/form" "gogs.io/gogs/internal/osutil" "gogs.io/gogs/internal/tool" + "gogs.io/gogs/internal/userutil" ) const ( @@ -269,7 +270,7 @@ func SettingsPost(c *context.Context, f form.RepoSetting) { log.Trace("Repository deleted: %s/%s", c.Repo.Owner.Name, repo.Name) c.Flash.Success(c.Tr("repo.settings.deletion_success")) - c.Redirect(c.Repo.Owner.DashboardLink()) + c.Redirect(userutil.DashboardURLPath(c.Repo.Owner.Name, c.Repo.Owner.IsOrganization())) case "delete-wiki": if !c.Repo.IsOwner() { diff --git a/internal/route/user/profile.go b/internal/route/user/profile.go index 25127028f..783fb63cc 100644 --- a/internal/route/user/profile.go +++ b/internal/route/user/profile.go @@ -118,7 +118,7 @@ func Action(c *context.Context, puser *context.ParamsUser) { redirectTo := c.Query("redirect_to") if !tool.IsSameSiteURLPath(redirectTo) { - redirectTo = puser.HomeLink() + redirectTo = puser.HomeURLPath() } c.Redirect(redirectTo) } diff --git a/internal/userutil/userutil.go b/internal/userutil/userutil.go new file mode 100644 index 000000000..87b8f15b8 --- /dev/null +++ b/internal/userutil/userutil.go @@ -0,0 +1,36 @@ +// Copyright 2022 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package userutil + +import ( + "encoding/hex" + "fmt" + "strings" + + "gogs.io/gogs/internal/conf" + "gogs.io/gogs/internal/tool" +) + +// DashboardURLPath returns the URL path to the user or organization dashboard. +func DashboardURLPath(name string, isOrganization bool) string { + if isOrganization { + return conf.Server.Subpath + "/org/" + name + "/dashboard/" + } + return conf.Server.Subpath + "/" +} + +// GenerateActivateCode generates an activate code based on user information and +// the given email. +func GenerateActivateCode(id int64, email, name, password, rands string) string { + code := tool.CreateTimeLimitCode( + fmt.Sprintf("%d%s%s%s%s", id, email, strings.ToLower(name), password, rands), + conf.Auth.ActivateCodeLives, + nil, + ) + + // Add tailing hex username + code += hex.EncodeToString([]byte(strings.ToLower(name))) + return code +} diff --git a/internal/userutil/userutil_test.go b/internal/userutil/userutil_test.go new file mode 100644 index 000000000..a62363a54 --- /dev/null +++ b/internal/userutil/userutil_test.go @@ -0,0 +1,40 @@ +// Copyright 2022 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package userutil + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "gogs.io/gogs/internal/conf" + "gogs.io/gogs/internal/tool" +) + +func TestDashboardURLPath(t *testing.T) { + t.Run("user", func(t *testing.T) { + got := DashboardURLPath("alice", false) + want := "/" + assert.Equal(t, want, got) + }) + + t.Run("organization", func(t *testing.T) { + got := DashboardURLPath("acme", true) + want := "/org/acme/dashboard/" + assert.Equal(t, want, got) + }) +} + +func TestGenerateActivateCode(t *testing.T) { + conf.SetMockAuth(t, + conf.AuthOpts{ + ActivateCodeLives: 10, + }, + ) + + code := GenerateActivateCode(1, "alice@example.com", "Alice", "123456", "rands") + got := tool.VerifyTimeLimitCode("1alice@example.comalice123456rands", conf.Auth.ActivateCodeLives, code[:tool.TIME_LIMIT_CODE_LENGTH]) + assert.True(t, got) +} diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl index 285aa12cf..9849a804b 100644 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -48,7 +48,7 @@ {{/*
  • - + {{.Owner.NumStars}} {{.i18n.Tr "user.starred"}}
  • @@ -56,7 +56,7 @@ {{if .Orgs}}
  • {{range .Orgs}} - + {{end}}
  • {{end}}