From 576b2d70d6dfdedc8ecc9cbf7fea0df3365a7ebf Mon Sep 17 00:00:00 2001 From: Kaspar Emanuel Date: Wed, 9 Feb 2022 22:29:43 +0000 Subject: [PATCH 1/9] Add a sync fork github workflow --- .github/workflows/sync-fork.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/sync-fork.yml diff --git a/.github/workflows/sync-fork.yml b/.github/workflows/sync-fork.yml new file mode 100644 index 000000000000..46ac2c12adbb --- /dev/null +++ b/.github/workflows/sync-fork.yml @@ -0,0 +1,24 @@ +name: Sync fork +# syncs our main branch with upstream main + +on: + # runs every monday at 6:21 UTC+0 + schedule: + - cron: '21 6 * * MON' + # allow triggers via button click + workflow_dispatch: + +jobs: + sync: + runs-on: ubuntu-20.04 + + steps: + - uses: tgymnich/fork-sync@v1.6.3 + with: + pr_title: Fork updates from go-gitea/gitea main + owner: go-gitea + base: main + head: main + auto_approve: false + auto_merge: true + token: ${{ secrets.GITHUB_TOKEN }} From 5d27a8c4ec14fab8ad2891f23c476c4323745339 Mon Sep 17 00:00:00 2001 From: Kaspar Emanuel Date: Tue, 8 Feb 2022 15:46:27 +0000 Subject: [PATCH 2/9] Improve migration API --- routers/api/v1/api.go | 5 +- routers/api/v1/repo/migrate.go | 96 +++++++++++++++++----------------- routers/web/repo/migrate.go | 2 +- services/task/task.go | 28 +++++----- 4 files changed, 67 insertions(+), 64 deletions(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 2c2926389021..18d7b9a1b9ae 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -772,7 +772,10 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route { m.Get("/issues/search", repo.SearchIssues) - m.Post("/migrate", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate) + m.Group("/migrate", func() { + m.Post("", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate) + m.Get("/status", repo.GetMigratingTask) + }) m.Group("/{username}/{reponame}", func() { m.Combo("").Get(reqAnyRepoReader(), repo.Get). diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go index d2c2d8ba1463..9fd2f6e64d43 100644 --- a/routers/api/v1/repo/migrate.go +++ b/routers/api/v1/repo/migrate.go @@ -5,8 +5,6 @@ package repo import ( - "bytes" - "errors" "fmt" "net/http" "strings" @@ -19,18 +17,16 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" - "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" base "code.gitea.io/gitea/modules/migration" - "code.gitea.io/gitea/modules/notification" - repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/migrations" + "code.gitea.io/gitea/services/task" ) // Migrate migrate remote git repository to gitea @@ -142,6 +138,7 @@ func Migrate(ctx *context.APIContext) { CloneAddr: remoteAddr, RepoName: form.RepoName, Description: form.Description, + OriginalURL: form.CloneAddr, Private: form.Private || setting.Repository.ForcePrivate, Mirror: form.Mirror, LFS: form.LFS, @@ -150,7 +147,7 @@ func Migrate(ctx *context.APIContext) { AuthPassword: form.AuthPassword, AuthToken: form.AuthToken, Wiki: form.Wiki, - Issues: form.Issues, + Issues: form.Issues || form.PullRequests, Milestones: form.Milestones, Labels: form.Labels, Comments: true, @@ -168,63 +165,33 @@ func Migrate(ctx *context.APIContext) { opts.Releases = false } - repo, err := repo_module.CreateRepository(ctx.Doer, repoOwner, models.CreateRepoOptions{ - Name: opts.RepoName, - Description: opts.Description, - OriginalURL: form.CloneAddr, - GitServiceType: gitServiceType, - IsPrivate: opts.Private, - IsMirror: opts.Mirror, - Status: repo_model.RepositoryBeingMigrated, - }) - if err != nil { - handleMigrateError(ctx, repoOwner, remoteAddr, err) + if err = repo_model.CheckCreateRepository(ctx.Doer, repoOwner, opts.RepoName, false); err != nil { + handleMigrateError(ctx, repoOwner, &opts, err) return } - opts.MigrateToRepoID = repo.ID - - defer func() { - if e := recover(); e != nil { - var buf bytes.Buffer - fmt.Fprintf(&buf, "Handler crashed with error: %v", log.Stack(2)) - - err = errors.New(buf.String()) - } - - if err == nil { - notification.NotifyMigrateRepository(ctx.Doer, repoOwner, repo) - return - } - - if repo != nil { - if errDelete := models.DeleteRepository(ctx.Doer, repoOwner.ID, repo.ID); errDelete != nil { - log.Error("DeleteRepository: %v", errDelete) - } - } - }() - - if repo, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), ctx.Doer, repoOwner.Name, opts, nil); err != nil { - handleMigrateError(ctx, repoOwner, remoteAddr, err) + repo, err := task.MigrateRepository(ctx.Doer, repoOwner, opts) + if err == nil { + log.Trace("Repository migrated: %s/%s", repoOwner.Name, form.RepoName) + ctx.JSON(http.StatusCreated, convert.ToRepo(repo, perm.AccessModeAdmin)) return } - log.Trace("Repository migrated: %s/%s", repoOwner.Name, form.RepoName) - ctx.JSON(http.StatusCreated, convert.ToRepo(repo, perm.AccessModeAdmin)) + handleMigrateError(ctx, repoOwner, &opts, err) } -func handleMigrateError(ctx *context.APIContext, repoOwner *user_model.User, remoteAddr string, err error) { +func handleMigrateError(ctx *context.APIContext, repoOwner *user_model.User, migrationOpts *migrations.MigrateOptions, err error) { switch { - case repo_model.IsErrRepoAlreadyExist(err): - ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.") - case repo_model.IsErrRepoFilesAlreadyExist(err): - ctx.Error(http.StatusConflict, "", "Files already exist for this repository. Adopt them or delete them.") case migrations.IsRateLimitError(err): ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit addressed rate limitation.") case migrations.IsTwoFactorAuthError(err): ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit required two factors authentication.") case repo_model.IsErrReachLimitOfRepo(err): ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit())) + case repo_model.IsErrRepoAlreadyExist(err): + ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.") + case repo_model.IsErrRepoFilesAlreadyExist(err): + ctx.Error(http.StatusConflict, "", "Files already exist for this repository. Adopt them or delete them.") case db.IsErrNameReserved(err): ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The username '%s' is reserved.", err.(db.ErrNameReserved).Name)) case db.IsErrNameCharsNotAllowed(err): @@ -270,3 +237,36 @@ func handleRemoteAddrError(ctx *context.APIContext, err error) { ctx.Error(http.StatusInternalServerError, "ParseRemoteAddr", err) } } + +// GetMigratingTask returns the migrating task by repo's id +func GetMigratingTask(ctx *context.APIContext) { + // swagger:operation GET /repos/migrate/status task + // --- + // summary: Get the migration status of a repository by its id + // produces: + // - application/json + // parameters: + // - name: repo_id + // in: query + // description: repository id + // type: int64 + // responses: + // "200": + // "$ref": "#/responses/" + // "404": + // "$ref": "#/response/" + t, err := models.GetMigratingTask(ctx.FormInt64("repo_id")) + + if err != nil { + ctx.JSON(http.StatusNotFound, err) + return + } + + ctx.JSON(200, map[string]interface{}{ + "status": t.Status, + "err": t.Message, + "repo-id": t.RepoID, + "start": t.StartTime, + "end": t.EndTime, + }) +} diff --git a/routers/web/repo/migrate.go b/routers/web/repo/migrate.go index 38cdbd49735d..0defb604d453 100644 --- a/routers/web/repo/migrate.go +++ b/routers/web/repo/migrate.go @@ -238,7 +238,7 @@ func MigratePost(ctx *context.Context) { return } - err = task.MigrateRepository(ctx.Doer, ctxUser, opts) + _, err = task.MigrateRepository(ctx.Doer, ctxUser, opts) if err == nil { ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(opts.RepoName)) return diff --git a/services/task/task.go b/services/task/task.go index 9deb0286c5be..dc749185a986 100644 --- a/services/task/task.go +++ b/services/task/task.go @@ -60,37 +60,37 @@ func handle(data ...queue.Data) []queue.Data { } // MigrateRepository add migration repository to task -func MigrateRepository(doer, u *user_model.User, opts base.MigrateOptions) error { - task, err := CreateMigrateTask(doer, u, opts) +func MigrateRepository(doer, u *user_model.User, opts base.MigrateOptions) (*repo_model.Repository, error) { + task, repo, err := createMigrationTask(doer, u, opts) if err != nil { - return err + return repo, err } - return taskQueue.Push(task) + return repo, taskQueue.Push(task) } -// CreateMigrateTask creates a migrate task -func CreateMigrateTask(doer, u *user_model.User, opts base.MigrateOptions) (*models.Task, error) { +// createMigrationTask creates a migrate task +func createMigrationTask(doer, u *user_model.User, opts base.MigrateOptions) (*models.Task, *repo_model.Repository, error) { // encrypt credentials for persistence var err error opts.CloneAddrEncrypted, err = secret.EncryptSecret(setting.SecretKey, opts.CloneAddr) if err != nil { - return nil, err + return nil, nil, err } opts.CloneAddr = util.SanitizeCredentialURLs(opts.CloneAddr) opts.AuthPasswordEncrypted, err = secret.EncryptSecret(setting.SecretKey, opts.AuthPassword) if err != nil { - return nil, err + return nil, nil, err } opts.AuthPassword = "" opts.AuthTokenEncrypted, err = secret.EncryptSecret(setting.SecretKey, opts.AuthToken) if err != nil { - return nil, err + return nil, nil, err } opts.AuthToken = "" bs, err := json.Marshal(&opts) if err != nil { - return nil, err + return nil, nil, err } task := &models.Task{ @@ -102,7 +102,7 @@ func CreateMigrateTask(doer, u *user_model.User, opts base.MigrateOptions) (*mod } if err := models.CreateTask(task); err != nil { - return nil, err + return nil, nil, err } repo, err := repo_module.CreateRepository(doer, u, models.CreateRepoOptions{ @@ -121,13 +121,13 @@ func CreateMigrateTask(doer, u *user_model.User, opts base.MigrateOptions) (*mod if err2 != nil { log.Error("UpdateCols Failed: %v", err2.Error()) } - return nil, err + return nil, nil, err } task.RepoID = repo.ID if err = task.UpdateCols("repo_id"); err != nil { - return nil, err + return nil, repo, err } - return task, nil + return task, repo, nil } From dbccb05d58907dd434bf1f9fa25cc7a037a3289f Mon Sep 17 00:00:00 2001 From: Kaspar Emanuel Date: Tue, 8 Feb 2022 15:49:02 +0000 Subject: [PATCH 3/9] Allow reading collaborators without API token --- routers/api/v1/api.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 18d7b9a1b9ae..3713ddcee925 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -806,9 +806,10 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route { m.Post("/tests", context.RepoRefForAPI, repo.TestHook) }) }, reqToken(), reqAdmin(), reqWebhooksEnabled()) + m.Get("/collaborators/{collaborator}", reqAnyRepoReader(), repo.IsCollaborator) m.Group("/collaborators", func() { m.Get("", reqAnyRepoReader(), repo.ListCollaborators) - m.Combo("/{collaborator}").Get(reqAnyRepoReader(), repo.IsCollaborator). + m.Combo("/{collaborator}"). Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator). Delete(reqAdmin(), repo.DeleteCollaborator) }, reqToken()) From bc103e6765edec0c51d21b900d5365ecc07dacc9 Mon Sep 17 00:00:00 2001 From: Kaspar Emanuel Date: Tue, 8 Feb 2022 15:52:52 +0000 Subject: [PATCH 4/9] Add a JSON file upload endpoint --- routers/web/repo/editor.go | 151 +++++++++++++++++++++++++++++++++++++ routers/web/web.go | 2 + 2 files changed, 153 insertions(+) diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index c10162c7595d..03d7a34cce99 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -725,6 +725,157 @@ func UploadFilePost(ctx *context.Context) { } } +// UploadFilePostJson JSON response for uploading file +func UploadFilePostJson(ctx *context.Context) { + // `renderCommitRights` is a poorly named; it's not responsible directly for rendering anything, + // it returns a boolean + form := web.GetForm(ctx).(*forms.UploadRepoFileForm) + canCommit := renderCommitRights(ctx) + oldBranchName := ctx.Repo.BranchName + branchName := oldBranchName + + if form.CommitChoice == frmCommitChoiceNewBranch { + branchName = form.NewBranchName + } + + form.TreePath = cleanUploadFileName(form.TreePath) + + treeNames, _ := getParentTreeFields(form.TreePath) + if len(treeNames) == 0 { + // We must at least have one element for user to input. + treeNames = []string{""} + } + response := make(map[string]string) + + if ctx.HasError() { + response["message"] = "Failed to commit the files" + ctx.JSON(http.StatusUnprocessableEntity, response) + log.Error("failed to commit files") + return + } + + if oldBranchName != branchName { + if _, err := ctx.Repo.GitRepo.GetBranch(ctx.Repo.BranchName); err == nil { + response["message"] = "Branch already exists" + ctx.JSON(http.StatusConflict, response) + return + } + } else if !canCommit { + response["message"] = "Can't commit to protected branch" + ctx.JSON(http.StatusUnauthorized, response) + return + } + + var newTreePath string + for _, part := range treeNames { + newTreePath = path.Join(newTreePath, part) + entry, err := ctx.Repo.Commit.GetTreeEntryByPath(newTreePath) + if err != nil { + if git.IsErrNotExist(err) { + // Means there is no item with that name, so we're good + break + } + response["message"] = "Something went wrong!" + ctx.JSON(http.StatusInternalServerError, response) + return + } + + // User can only upload files to a directory. + if !entry.IsDir() { + ctx.Data["Err_TreePath"] = true + ctx.RenderWithErr(ctx.Tr("repo.editor.directory_is_a_file", part), tplUploadFile, &form) + response["message"] = "A file with the same name of the new directory already exists." + ctx.JSON(http.StatusConflict, response) + return + } + } + + message := strings.TrimSpace(form.CommitSummary) + if len(message) == 0 { + message = ctx.Tr("repo.editor.upload_files_to_dir", form.TreePath) + } + + form.CommitMessage = strings.TrimSpace(form.CommitMessage) + if len(form.CommitMessage) > 0 { + message += "\n\n" + form.CommitMessage + } + + if err := files_service.UploadRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.UploadRepoFileOptions{ + LastCommitID: ctx.Repo.CommitID, + OldBranch: oldBranchName, + NewBranch: branchName, + TreePath: form.TreePath, + Message: message, + Files: form.Files, + }); err != nil { + if models.IsErrLFSFileLocked(err) { + response["message"] = ctx.Tr("repo.editor.upload_file_is_locked") + ctx.JSON(http.StatusUnauthorized, response) + } else if models.IsErrFilenameInvalid(err) { + response["message"] = ctx.Tr("repo.editor.filename_is_invalid") + ctx.JSON(http.StatusUnprocessableEntity, response) + } else if models.IsErrFilePathInvalid(err) { + fileErr := err.(models.ErrFilePathInvalid) + switch fileErr.Type { + case git.EntryModeSymlink: + response["message"] = ctx.Tr("repo.editor.file_is_a_symlink", fileErr.Path) + ctx.JSON(http.StatusConflict, response) + case git.EntryModeTree: + response["message"] = ctx.Tr("repo.editor.filename_is_a_directory", fileErr.Path) + ctx.JSON(http.StatusConflict, response) + case git.EntryModeBlob: + response["message"] = ctx.Tr("repo.editor.directory_is_a_file", fileErr.Path) + ctx.JSON(http.StatusConflict, response) + default: + response["message"] = "Something went wrong" + ctx.JSON(http.StatusInternalServerError, response) + } + } else if models.IsErrRepoFileAlreadyExists(err) { + response["message"] = ctx.Tr("repo.editor.file_already_exists", form.TreePath) + ctx.JSON(http.StatusConflict, response) + } else if git.IsErrBranchNotExist(err) { + branchErr := err.(git.ErrBranchNotExist) + response["message"] = ctx.Tr("repo.editor.branch_does_not_exist", branchErr.Name) + ctx.JSON(http.StatusNotFound, response) + } else if models.IsErrBranchAlreadyExists(err) { + // For when a user specifies a new branch that already exists + branchErr := err.(models.ErrBranchAlreadyExists) + response["message"] = ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName) + ctx.JSON(http.StatusConflict, response) + } else if git.IsErrPushOutOfDate(err) { + response["message"] = ctx.Tr( + "repo.editor.file_changed_while_editing", + ctx.Repo.RepoLink+"/compare/"+ctx.Repo.CommitID+"..."+form.NewBranchName, + ) + ctx.JSON(http.StatusConflict, response) + } else if git.IsErrPushRejected(err) { + errPushRej := err.(*git.ErrPushRejected) + if len(errPushRej.Message) == 0 { + response["message"] = ctx.Tr("repo.editor.push_rejected_no_message") + ctx.JSON(http.StatusConflict, response) + } else { + response["response"] = ctx.Tr("repo.editor.push_rejected", utils.SanitizeFlashErrorString(errPushRej.Message)) + ctx.JSON(http.StatusConflict, response) + } + } else { + // os.ErrNotExist - upload file missing in the intervening time?! + log.Error( + "Error during upload to repo: %-v to filepath: %s on %s from %s: %v", + ctx.Repo.Repository, + form.TreePath, + oldBranchName, + form.NewBranchName, + err, + ) + response["message"] = ctx.Tr("repo.editor.unable_to_upload_files", form.TreePath, err) + ctx.JSON(http.StatusInternalServerError, response) + } + return + } + response["message"] = "Committed files successfully." + ctx.JSON(http.StatusCreated, response) +} + func cleanUploadFileName(name string) string { // Rebase the filename name = strings.Trim(path.Clean("/"+name), "/") diff --git a/routers/web/web.go b/routers/web/web.go index 60e104ccf83b..b32293dc07ca 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -894,6 +894,8 @@ func RegisterRoutes(m *web.Route) { Post(bindIgnErr(forms.EditRepoFileForm{}), repo.NewDiffPatchPost) m.Combo("/_cherrypick/{sha:([a-f0-9]{7,40})}/*").Get(repo.CherryPick). Post(bindIgnErr(forms.CherryPickForm{}), repo.CherryPickPost) + // Same as `/_upload/*` but returns JSON + m.Post("/upload/*", repo.MustBeAbleToUpload, bindIgnErr(forms.UploadRepoFileForm{}), repo.UploadFilePostJson) }, context.RepoRefByType(context.RepoRefBranch), repo.MustBeEditable) m.Group("", func() { m.Post("/upload-file", repo.UploadFileToServer) From 096794cb4ffe743477ad5ff1c2bef2d16d241351 Mon Sep 17 00:00:00 2001 From: Kaspar Emanuel Date: Tue, 8 Feb 2022 15:53:07 +0000 Subject: [PATCH 5/9] Add JSON sign_up and sign_in endpoints --- routers/web/auth/kitspace_auth.go | 189 ++++++++++++++++++++++++++++++ routers/web/web.go | 5 + 2 files changed, 194 insertions(+) create mode 100644 routers/web/auth/kitspace_auth.go diff --git a/routers/web/auth/kitspace_auth.go b/routers/web/auth/kitspace_auth.go new file mode 100644 index 000000000000..fbd823650cf8 --- /dev/null +++ b/routers/web/auth/kitspace_auth.go @@ -0,0 +1,189 @@ +package auth + +import ( + "net/http" + + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/password" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/auth" + "code.gitea.io/gitea/services/forms" + "code.gitea.io/gitea/services/mailer" +) + +// KitspaceSignUp custom sign-up compatible with Kitspace architecture +func KitspaceSignUp(ctx *context.Context) { + // swagger:operation POST /user/kitspace/sign_up + // --- + // summary: Create a user + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/RegisterForm" + // responses: + // "201": + // "$ref": "#/responses/User" + // "400": + // "$ref": "#/responses/error" + // "409": + // "$ref": "#/response/error + // "422": + // "$ref": "#/responses/validationError" + response := make(map[string]interface{}) + form := web.GetForm(ctx).(*forms.RegisterForm) + + if len(form.Password) < setting.MinPasswordLength { + response["error"] = "UnprocessableEntity" + response["message"] = "Password is too short." + + ctx.JSON(http.StatusUnprocessableEntity, response) + return + } + + if !password.IsComplexEnough(form.Password) { + response["error"] = "UnprocessableEntity" + response["message"] = "Password isn't complex enough." + + ctx.JSON(http.StatusUnprocessableEntity, response) + return + } + + u := &user_model.User{ + Name: form.UserName, + Email: form.Email, + Passwd: form.Password, + IsActive: !setting.Service.RegisterEmailConfirm, + } + + if err := user_model.CreateUser(u); err != nil { + switch { + case user_model.IsErrUserAlreadyExist(err): + response["error"] = "Conflict" + response["message"] = "User already exists." + + ctx.JSON(http.StatusConflict, response) + case user_model.IsErrEmailAlreadyUsed(err): + response["error"] = "Conflict" + response["message"] = "Email is already used." + + ctx.JSON(http.StatusConflict, response) + case db.IsErrNameReserved(err): + response["error"] = "Conflict" + response["message"] = "Name is reserved." + + ctx.JSON(http.StatusConflict, response) + case db.IsErrNamePatternNotAllowed(err): + response["error"] = "UnprocessableEntity" + response["message"] = "This name pattern isn't allowed." + + ctx.JSON(http.StatusUnprocessableEntity, response) + default: + ctx.ServerError("Signup", err) + } + return + } else { + log.Trace("Account created: %s", u.Name) + } + + // Send confirmation email + // The mailing service works only in production during development no mails are sent + if setting.Service.RegisterEmailConfirm && u.ID > 1 { + mailer.SendActivateAccountMail(ctx.Locale, u) + + if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { + log.Error("Set cache(MailResendLimit) fail: %v", err) + } + } + + handleSignInFull(ctx, u, true, false) + + // Return the success response with user details + response["user"] = convert.ToUser(u, u) + + ctx.JSON(http.StatusCreated, response) +} + +// KitspaceSignIn custom sign-in compatible with Kitspace architecture +func KitspaceSignIn(ctx *context.Context) { + // swagger:operation POST /user/kitspace/sign_in + // --- + // summary: login a user + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/SignInForm" + // responses: + // "200": + // "$ref": "success" + // "404": + // "$ref": "#/response/forbidden" + // "404": + // "$ref": "#/responses/notFound" + // "409": + // "$ref": "#/response/error + // "422": + // "$ref": "#/responses/validationError" + + form := web.GetForm(ctx).(*forms.SignInForm) + u, _, err := auth.UserSignIn(form.UserName, form.Password) + + response := make(map[string]interface{}) + if err != nil { + switch { + case user_model.IsErrUserNotExist(err): + response["error"] = "Not Found" + response["message"] = "Wrong username or password." + + ctx.JSON(http.StatusNotFound, response) + log.Info("Failed authentication attempt for %s from %s", form.UserName, ctx.RemoteAddr()) + case user_model.IsErrEmailAlreadyUsed(err): + response["error"] = "Conflict" + response["message"] = "This email has already been used." + + ctx.JSON(http.StatusConflict, response) + log.Info("Failed authentication attempt for %s from %s", form.UserName, ctx.RemoteAddr()) + case user_model.IsErrUserProhibitLogin(err): + response["error"] = "Prohibited" + response["message"] = "Prohibited login." + + ctx.JSON(http.StatusForbidden, response) + log.Info("Failed authentication attempt for %s from %s", form.UserName, ctx.RemoteAddr()) + case user_model.IsErrUserInactive(err): + if setting.Service.RegisterEmailConfirm { + response["error"] = "ActivationRequired" + response["message"] = "Activate your account." + + ctx.JSON(http.StatusOK, response) + } else { + response["error"] = "Prohibited" + response["message"] = "Prohibited login" + + ctx.JSON(http.StatusForbidden, response) + log.Info("Failed authentication attempt for %s from %s", form.UserName, ctx.RemoteAddr()) + } + default: + ctx.ServerError("KitspaceSignIn", err) + } + return + } + handleSignInFull(ctx, u, form.Remember, false) + + response["user"] = convert.ToUser(u, u) + + ctx.JSON(http.StatusOK, response) +} diff --git a/routers/web/web.go b/routers/web/web.go index b32293dc07ca..b04cd4055ab3 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -296,6 +296,11 @@ func RegisterRoutes(m *web.Route) { // ***** START: User ***** m.Group("/user", func() { + m.Group("/kitspace", func() { + m.Post("/sign_up", bindIgnErr(forms.RegisterForm{}), auth.KitspaceSignUp) + m.Post("/sign_in", bindIgnErr(forms.SignInForm{}), auth.KitspaceSignIn) + }) + m.Get("/login", auth.SignIn) m.Post("/login", bindIgnErr(forms.SignInForm{}), auth.SignInPost) m.Group("", func() { From 20797f27ac90ce98ac92e18ba9cc0386dfbc078f Mon Sep 17 00:00:00 2001 From: Kaspar Emanuel Date: Sun, 24 Feb 2019 12:26:37 +0000 Subject: [PATCH 6/9] Add a kitspace session endpoint --- routers/web/auth/kitspace_auth.go | 25 +++++++++++++++++++++++++ routers/web/web.go | 1 + 2 files changed, 26 insertions(+) diff --git a/routers/web/auth/kitspace_auth.go b/routers/web/auth/kitspace_auth.go index fbd823650cf8..e86428d20ab5 100644 --- a/routers/web/auth/kitspace_auth.go +++ b/routers/web/auth/kitspace_auth.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/password" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/forms" @@ -187,3 +188,27 @@ func KitspaceSignIn(ctx *context.Context) { ctx.JSON(http.StatusOK, response) } + +func GetKitspaceSession(ctx *context.Context) { + // swagger:operation GET /user/kitspace/session + // --- + // summary: get currently signed in user (if any) and csrf token + // consumes: + // - application/json + // produces: + // - application/json + // responses: + // "200": + // "$ref": "success" + + var user *structs.User + if ctx.Doer != nil && ctx.IsSigned { + user = convert.ToUser(ctx.Doer, ctx.Doer) + } + + response := make(map[string]interface{}) + response["user"] = user + response["csrf"] = ctx.Data["CsrfToken"] + + ctx.JSON(http.StatusOK, response) +} diff --git a/routers/web/web.go b/routers/web/web.go index b04cd4055ab3..f2ab215c37b8 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -295,6 +295,7 @@ func RegisterRoutes(m *web.Route) { m.Get("/milestones", reqSignIn, reqMilestonesDashboardPageEnabled, user.Milestones) // ***** START: User ***** + m.Get("/user/kitspace/session", auth.GetKitspaceSession) m.Group("/user", func() { m.Group("/kitspace", func() { m.Post("/sign_up", bindIgnErr(forms.RegisterForm{}), auth.KitspaceSignUp) From 8a8b947c958a4758392f88a2951915c7d803d537 Mon Sep 17 00:00:00 2001 From: Kaspar Emanuel Date: Tue, 8 Feb 2022 15:54:34 +0000 Subject: [PATCH 7/9] Add a development dockerfile --- Dockerfile | 2 +- Dockerfile.gitea.dev | 66 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 Dockerfile.gitea.dev diff --git a/Dockerfile b/Dockerfile index 973d93b784cc..8cb88ae83e4f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ #Build stage -FROM golang:1.18-alpine3.15 AS build-env +FROM golang:1.17-alpine3.15 AS build-env ARG GOPROXY ENV GOPROXY ${GOPROXY:-direct} diff --git a/Dockerfile.gitea.dev b/Dockerfile.gitea.dev new file mode 100644 index 000000000000..49e3825d11d1 --- /dev/null +++ b/Dockerfile.gitea.dev @@ -0,0 +1,66 @@ +FROM golang:1.17-alpine3.15 + +ARG GOPROXY +ENV GOPROXY ${GOPROXY:-direct} + +ARG TAGS="sqlite sqlite_unlock_notify" +ENV TAGS "bindata timetzdata $TAGS" +ARG CGO_EXTRA_CFLAGS + +#Build & runtime deps +RUN apk --no-cache add \ + build-base \ + git \ + nodejs \ + npm \ + bash \ + ca-certificates \ + curl \ + gettext \ + git \ + linux-pam \ + openssh \ + s6 \ + sqlite \ + su-exec \ + gnupg + + +#Setup repo +COPY . /go/src/code.gitea.io/gitea +WORKDIR /go/src/code.gitea.io/gitea + +RUN npm install --no-save +RUN make build + +# Begin env-to-ini build +RUN go build contrib/environment-to-ini/environment-to-ini.go + +EXPOSE 22 3000 + +RUN addgroup \ + -S -g 1000 \ + git && \ + adduser \ + -S -H -D \ + -h /data/git \ + -s /bin/bash \ + -u 1000 \ + -G git \ + git && \ + echo "git:*" | chpasswd -e + +ENV USER git +ENV GITEA_CUSTOM /data/gitea + +VOLUME ["/data"] + +ENTRYPOINT ["/usr/bin/entrypoint"] +CMD ["/bin/s6-svscan", "/etc/s6"] + +COPY docker/root / +RUN mkdir -p /app/gitea +RUN cp /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea +RUN cp /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini +RUN chmod 755 /usr/bin/entrypoint /app/gitea/gitea /usr/local/bin/gitea /usr/local/bin/environment-to-ini +RUN chmod 755 /etc/s6/gitea/* /etc/s6/openssh/* /etc/s6/.s6-svscan/* From 7e9ecfefae722c07246ea166994baa1b1f102f82 Mon Sep 17 00:00:00 2001 From: Kaspar Emanuel Date: Sat, 12 Feb 2022 12:43:55 +0000 Subject: [PATCH 8/9] Remove Github issue templates --- .github/ISSUE_TEMPLATE/bug-report.yaml | 94 --------------------- .github/ISSUE_TEMPLATE/config.yml | 17 ---- .github/ISSUE_TEMPLATE/feature-request.yaml | 24 ------ .github/ISSUE_TEMPLATE/ui.bug-report.yaml | 66 --------------- 4 files changed, 201 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug-report.yaml delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature-request.yaml delete mode 100644 .github/ISSUE_TEMPLATE/ui.bug-report.yaml diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml deleted file mode 100644 index 9dacad0d5fb8..000000000000 --- a/.github/ISSUE_TEMPLATE/bug-report.yaml +++ /dev/null @@ -1,94 +0,0 @@ -name: Bug Report -description: Found something you weren't expecting? Report it here! -labels: kind/bug -body: -- type: markdown - attributes: - value: | - NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue. -- type: markdown - attributes: - value: | - 1. Please speak English, this is the language all maintainers can speak and write. - 2. Please ask questions or configuration/deploy problems on our Discord - server (https://discord.gg/gitea) or forum (https://discourse.gitea.io). - 3. Make sure you are using the latest release and - take a moment to check that your issue hasn't been reported before. - 4. Make sure it's not mentioned in the FAQ (https://docs.gitea.io/en-us/faq) - 5. Please give all relevant information below for bug reports, because - incomplete details will be handled as an invalid report. - 6. In particular it's really important to provide pertinent logs. You must give us DEBUG level logs. - Please read https://docs.gitea.io/en-us/logging-configuration/#debugging-problems - In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini -- type: textarea - id: description - attributes: - label: Description - description: | - Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below) - If you are using a proxy or a CDN (e.g. Cloudflare) in front of Gitea, please disable the proxy/CDN fully and access Gitea directly to confirm the issue still persists without those services. -- type: input - id: gitea-ver - attributes: - label: Gitea Version - description: Gitea version (or commit reference) of your instance - validations: - required: true -- type: dropdown - id: can-reproduce - attributes: - label: Can you reproduce the bug on the Gitea demo site? - description: | - If so, please provide a URL in the Description field - URL of Gitea demo: https://try.gitea.io - options: - - "Yes" - - "No" - validations: - required: true -- type: markdown - attributes: - value: | - It's really important to provide pertinent logs - Please read https://docs.gitea.io/en-us/logging-configuration/#debugging-problems - In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini -- type: input - id: logs - attributes: - label: Log Gist - description: Please provide a gist URL of your logs, with any sensitive information (e.g. API keys) removed/hidden -- type: textarea - id: screenshots - attributes: - label: Screenshots - description: If this issue involves the Web Interface, please provide one or more screenshots -- type: input - id: git-ver - attributes: - label: Git Version - description: The version of git running on the server -- type: input - id: os-ver - attributes: - label: Operating System - description: The operating system you are using to run Gitea -- type: textarea - id: run-info - attributes: - label: How are you running Gitea? - description: | - Please include information on whether you built Gitea yourself, used one of our downloads, are using https://try.gitea.io or are using some other package - Please also tell us how you are running Gitea, e.g. if it is being run from docker, a command-line, systemd etc. - If you are using a package or systemd tell us what distribution you are using - validations: - required: true -- type: dropdown - id: database - attributes: - label: Database - description: What database system are you running? - options: - - PostgreSQL - - MySQL - - MSSQL - - SQLite diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index e79cc9d4328d..000000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,17 +0,0 @@ -blank_issues_enabled: true -contact_links: - - name: Security Concern - url: https://tinyurl.com/security-gitea - about: For security concerns, please send a mail to security@gitea.io instead of opening a public issue. - - name: Discord Server - url: https://discord.gg/gitea - about: Please ask questions and discuss configuration or deployment problems here. - - name: Discourse Forum - url: https://discourse.gitea.io - about: Questions and configuration or deployment problems can also be discussed on our forum. - - name: Frequently Asked Questions - url: https://docs.gitea.io/en-us/faq - about: Please check if your question isn't mentioned here. - - name: Crowdin Translations - url: https://crowdin.com/project/gitea - about: Translations are managed here. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yaml b/.github/ISSUE_TEMPLATE/feature-request.yaml deleted file mode 100644 index 37f57c8f23df..000000000000 --- a/.github/ISSUE_TEMPLATE/feature-request.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: Feature Request -description: Got an idea for a feature that Gitea doesn't have currently? Submit your idea here! -labels: ["kind/feature", "kind/proposal"] -body: -- type: markdown - attributes: - value: | - 1. Please speak English, this is the language all maintainers can speak and write. - 2. Please ask questions or configuration/deploy problems on our Discord - server (https://discord.gg/gitea) or forum (https://discourse.gitea.io). - 3. Please take a moment to check that your feature hasn't already been suggested. -- type: textarea - id: description - attributes: - label: Feature Description - placeholder: | - I think it would be great if Gitea had... - validations: - required: true -- type: textarea - id: screenshots - attributes: - label: Screenshots - description: If you can, provide screenshots of an implementation on another site e.g. GitHub diff --git a/.github/ISSUE_TEMPLATE/ui.bug-report.yaml b/.github/ISSUE_TEMPLATE/ui.bug-report.yaml deleted file mode 100644 index 80db52d7f119..000000000000 --- a/.github/ISSUE_TEMPLATE/ui.bug-report.yaml +++ /dev/null @@ -1,66 +0,0 @@ -name: Web Interface Bug Report -description: Something doesn't look quite as it should? Report it here! -labels: ["kind/bug", "kind/ui"] -body: -- type: markdown - attributes: - value: | - NOTE: If your issue is a security concern, please send an email to security@gitea.io instead of opening a public issue. -- type: markdown - attributes: - value: | - 1. Please speak English, this is the language all maintainers can speak and write. - 2. Please ask questions or configuration/deploy problems on our Discord - server (https://discord.gg/gitea) or forum (https://discourse.gitea.io). - 3. Please take a moment to check that your issue doesn't already exist. - 4. Make sure it's not mentioned in the FAQ (https://docs.gitea.io/en-us/faq) - 5. Please give all relevant information below for bug reports, because - incomplete details will be handled as an invalid report. - 6. In particular it's really important to provide pertinent logs. If you are certain that this is a javascript - error, show us the javascript console. If the error appears to relate to Gitea the server you must also give us - DEBUG level logs. (See https://docs.gitea.io/en-us/logging-configuration/#debugging-problems) -- type: textarea - id: description - attributes: - label: Description - description: | - Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below) - If using a proxy or a CDN (e.g. CloudFlare) in front of gitea, please disable the proxy/CDN fully and connect to gitea directly to confirm the issue still persists without those services. -- type: textarea - id: screenshots - attributes: - label: Screenshots - description: Please provide at least 1 screenshot showing the issue. - validations: - required: true -- type: input - id: gitea-ver - attributes: - label: Gitea Version - description: Gitea version (or commit reference) your instance is running - validations: - required: true -- type: dropdown - id: can-reproduce - attributes: - label: Can you reproduce the bug on the Gitea demo site? - description: | - If so, please provide a URL in the Description field - URL of Gitea demo: https://try.gitea.io - options: - - "Yes" - - "No" - validations: - required: true -- type: input - id: os-ver - attributes: - label: Operating System - description: The operating system you are using to access Gitea -- type: input - id: browser-ver - attributes: - label: Browser Version - description: The browser and version that you are using to access Gitea - validations: - required: true From a4c0d1be6433441848dabb07e9df414e561a2b59 Mon Sep 17 00:00:00 2001 From: Abdulrhmn Ghanem Date: Thu, 31 Mar 2022 14:08:34 +0200 Subject: [PATCH 9/9] Fix: add missing validation steps - `!form.IsEmailDomainAllowed` - `pwned` --- routers/web/auth/kitspace_auth.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/routers/web/auth/kitspace_auth.go b/routers/web/auth/kitspace_auth.go index e86428d20ab5..3f73ee28bb4c 100644 --- a/routers/web/auth/kitspace_auth.go +++ b/routers/web/auth/kitspace_auth.go @@ -43,6 +43,14 @@ func KitspaceSignUp(ctx *context.Context) { response := make(map[string]interface{}) form := web.GetForm(ctx).(*forms.RegisterForm) + if !form.IsEmailDomainAllowed() { + response["error"] = "UnprocessableEntity" + response["message"] = "Email domain is blacklisted." + + ctx.JSON(http.StatusUnprocessableEntity, response) + return + } + if len(form.Password) < setting.MinPasswordLength { response["error"] = "UnprocessableEntity" response["message"] = "Password is too short." @@ -59,6 +67,21 @@ func KitspaceSignUp(ctx *context.Context) { return } + pwned, err := password.IsPwned(ctx, form.Password) + if pwned { + errMsg := ctx.Tr("auth.password_pwned") + if err != nil { + log.Error(err.Error()) + errMsg = ctx.Tr("auth.password_pwned_err") + ctx.ServerError(errMsg, err) + return + } + response["error"] = "UnprocessableEntity" + response["message"] = errMsg + ctx.JSON(http.StatusUnprocessableEntity, response) + return + } + u := &user_model.User{ Name: form.UserName, Email: form.Email,