mirror of
https://github.com/go-gitea/gitea.git
synced 2025-02-20 11:43:57 +08:00
Compare commits
6 Commits
d8492a1e9f
...
f16587a0d2
Author | SHA1 | Date | |
---|---|---|---|
|
f16587a0d2 | ||
|
21af8150b7 | ||
|
40faa6dc78 | ||
|
e4cd1ebbdd | ||
|
b366b328f5 | ||
|
da19f04a6f |
@ -210,3 +210,35 @@ func upsertUserSettingValue(ctx context.Context, userID int64, key, value string
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
type RepositoryRandsType string
|
||||
|
||||
const (
|
||||
RepositoryRandsTypeNewIssue RepositoryRandsType = "new_issue"
|
||||
)
|
||||
|
||||
func CreatRandsForRepository(ctx context.Context, userID, repoID int64, event RepositoryRandsType) (string, error) {
|
||||
rand, err := GetUserSalt()
|
||||
if err != nil {
|
||||
return rand, err
|
||||
}
|
||||
|
||||
return rand, SetUserSetting(ctx, userID, SettingsKeyUserRandsForRepo(repoID, string(event)), rand)
|
||||
}
|
||||
|
||||
func GetRandsForRepository(ctx context.Context, userID, repoID int64, event RepositoryRandsType) (string, error) {
|
||||
return GetSetting(ctx, userID, SettingsKeyUserRandsForRepo(repoID, string(event)))
|
||||
}
|
||||
|
||||
func (u *User) GetOrCreateRandsForRepository(ctx context.Context, repoID int64, event RepositoryRandsType) (string, error) {
|
||||
rand, err := GetRandsForRepository(ctx, u.ID, repoID, event)
|
||||
if err != nil && !IsErrUserSettingIsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(rand) == 0 || err != nil {
|
||||
rand, err = CreatRandsForRepository(ctx, u.ID, repoID, event)
|
||||
}
|
||||
|
||||
return rand, err
|
||||
}
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
package user
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
// SettingsKeyHiddenCommentTypes is the setting key for hidden comment types
|
||||
SettingsKeyHiddenCommentTypes = "issue.hidden_comment_types"
|
||||
@ -19,3 +21,11 @@ const (
|
||||
// SignupUserAgent is the user agent that the user signed up with
|
||||
SignupUserAgent = "signup.user_agent"
|
||||
)
|
||||
|
||||
func SettingsKeyUserRands(key string) string {
|
||||
return "rands." + key
|
||||
}
|
||||
|
||||
func SettingsKeyUserRandsForRepo(repoID int64, key string) string {
|
||||
return SettingsKeyUserRands(fmt.Sprintf("repo.%d.%s", repoID, key))
|
||||
}
|
||||
|
@ -1810,6 +1810,13 @@ issues.content_history.delete_from_history_confirm = Delete from history?
|
||||
issues.content_history.options = Options
|
||||
issues.reference_link = Reference: %s
|
||||
|
||||
issues.mailto_modal.title = Create new issue by email
|
||||
issues.mailto_modal.desc_1 = You can create a new issue inside this project by sending an email to the following email address:
|
||||
issues.mailto_modal.desc_2 = The subject will be used as the title of the new issue, and the message will be the description.
|
||||
issues.mailto_modal.desc_3 = `This is a private email address generated just for you. Anyone who has it can create issues as if they were you. If that happens, <a href="#" class="%s">reset this token</a>.`
|
||||
issues.mailto_modal.mailto_link = Email a new issue to this repository
|
||||
issues.mailto_modal.send_mail = send mail
|
||||
|
||||
compare.compare_base = base
|
||||
compare.compare_head = compare
|
||||
|
||||
|
@ -1701,7 +1701,9 @@ issues.time_estimate_invalid=O formato da estimativa de tempo é inválido
|
||||
issues.start_tracking_history=começou a trabalhar %s
|
||||
issues.tracker_auto_close=O cronómetro será parado automaticamente quando esta questão for fechada
|
||||
issues.tracking_already_started=`Você já iniciou a contagem de tempo <a href="%s">noutra questão</a>!`
|
||||
issues.stop_tracking=Parar cronómetro
|
||||
issues.stop_tracking_history=trabalhou durante <b>%[1]s</b> %[2]s
|
||||
issues.cancel_tracking=Descartar
|
||||
issues.cancel_tracking_history=`cancelou a contagem de tempo %s`
|
||||
issues.del_time=Eliminar este registo de tempo
|
||||
issues.add_time_history=adicionou <b>%[1]s</b> de tempo gasto %[2]s
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
"code.gitea.io/gitea/services/mailer/incoming"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
)
|
||||
|
||||
@ -780,5 +781,36 @@ func Issues(ctx *context.Context) {
|
||||
|
||||
ctx.Data["CanWriteIssuesOrPulls"] = ctx.Repo.CanWriteIssuesOrPulls(isPullList)
|
||||
|
||||
if !isPullList {
|
||||
err := renderMailToIssue(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("renderMailToIssue", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, tplIssues)
|
||||
}
|
||||
|
||||
func renderMailToIssue(ctx *context.Context) error {
|
||||
if !setting.IncomingEmail.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !ctx.IsSigned {
|
||||
return nil
|
||||
}
|
||||
|
||||
token, mailToAddress, err := incoming.GenerateMailToRepoURL(ctx, ctx.Doer, ctx.Repo.Repository, user_model.RepositoryRandsTypeNewIssue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Data["MailToIssueEnabled"] = true
|
||||
ctx.Data["MailToIssueAddress"] = mailToAddress
|
||||
ctx.Data["MailToIssueLink"] = fmt.Sprintf("mailto:%s", mailToAddress)
|
||||
ctx.Data["MailToIssueToken"] = token
|
||||
ctx.Data["MailToIssueTokenResetUrl"] = fmt.Sprintf("%s/user/settings/repo_mailto_rands_reset/%d", setting.AppSubURL, ctx.Repo.Repository.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
45
routers/web/repo/issue_list_test.go
Normal file
45
routers/web/repo/issue_list_test.go
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/contexttest"
|
||||
"code.gitea.io/gitea/services/mailer/token"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRenderMailToIssue(t *testing.T) {
|
||||
unittest.PrepareTestEnv(t)
|
||||
|
||||
ctx, _ := contexttest.MockContext(t, "user2/repo1")
|
||||
|
||||
ctx.IsSigned = true
|
||||
ctx.Doer = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
ctx.Repo = &context.Repository{
|
||||
Repository: unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}),
|
||||
}
|
||||
|
||||
setting.IncomingEmail.Enabled = true
|
||||
setting.IncomingEmail.ReplyToAddress = "test%{token}@gitea.io"
|
||||
setting.IncomingEmail.TokenPlaceholder = "%{token}"
|
||||
|
||||
err := renderMailToIssue(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
key, ok := ctx.Data["MailToIssueToken"].(string)
|
||||
assert.True(t, ok)
|
||||
|
||||
handlerType, user, _, err := token.ExtractToken(ctx, key)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, token.NewIssueHandlerType, handlerType)
|
||||
assert.EqualValues(t, ctx.Doer.ID, user.ID)
|
||||
}
|
37
routers/web/user/setting/repo.go
Normal file
37
routers/web/user/setting/repo.go
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/mailer/incoming"
|
||||
)
|
||||
|
||||
func ResetRepoMailToRands(ctx *context.Context) {
|
||||
repoID, _ := strconv.ParseInt(ctx.PathParam("repo_id"), 10, 64)
|
||||
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRepositoryByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = user_model.CreatRandsForRepository(ctx, ctx.Doer.ID, repo.ID, user_model.RepositoryRandsTypeNewIssue)
|
||||
if err != nil {
|
||||
ctx.ServerError("CreatRandsForRepository", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, url, err := incoming.GenerateMailToRepoURL(ctx, ctx.Doer, repo, user_model.RepositoryRandsTypeNewIssue)
|
||||
if err != nil {
|
||||
ctx.ServerError("GenerateMailToRepoURL", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]string{"url": url})
|
||||
}
|
@ -683,6 +683,8 @@ func registerRoutes(m *web.Router) {
|
||||
m.Get("", user_setting.BlockedUsers)
|
||||
m.Post("", web.Bind(forms.BlockUserForm{}), user_setting.BlockedUsersPost)
|
||||
})
|
||||
|
||||
m.Post("/repo_mailto_rands_reset/{repo_id}", user_setting.ResetRepoMailToRands)
|
||||
}, reqSignIn, ctxDataSet("PageIsUserSettings", true, "EnablePackages", setting.Packages.Enabled))
|
||||
|
||||
m.Group("/user", func() {
|
||||
|
@ -255,6 +255,7 @@ loop:
|
||||
}
|
||||
|
||||
content := getContentFromMailReader(env)
|
||||
content.Subject = env.GetHeader("Subject")
|
||||
|
||||
if err := handler.Handle(ctx, content, user, payload); err != nil {
|
||||
return fmt.Errorf("could not handle message: %w", err)
|
||||
@ -350,6 +351,7 @@ func searchTokenInAddresses(addresses []*net_mail.Address) string {
|
||||
type MailContent struct {
|
||||
Content string
|
||||
Attachments []*Attachment
|
||||
Subject string
|
||||
}
|
||||
|
||||
type Attachment struct {
|
||||
|
@ -6,11 +6,13 @@ package incoming
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@ -28,8 +30,10 @@ type MailHandler interface {
|
||||
}
|
||||
|
||||
var handlers = map[token.HandlerType]MailHandler{
|
||||
token.ReplyHandlerType: &ReplyHandler{},
|
||||
token.UnsubscribeHandlerType: &UnsubscribeHandler{},
|
||||
token.ReplyHandlerType: &ReplyHandler{},
|
||||
token.UnsubscribeHandlerType: &UnsubscribeHandler{},
|
||||
token.NewIssueHandlerType: &NewIssueHandler{},
|
||||
token.NewPullRequestHandlerType: &NewPullRequest{},
|
||||
}
|
||||
|
||||
// ReplyHandler handles incoming emails to create a reply from them
|
||||
@ -178,3 +182,79 @@ func (h *UnsubscribeHandler) Handle(ctx context.Context, _ *MailContent, doer *u
|
||||
|
||||
return fmt.Errorf("unsupported unsubscribe reference: %v", ref)
|
||||
}
|
||||
|
||||
// NewIssueHandler handles new issues
|
||||
type NewIssueHandler struct{}
|
||||
|
||||
func (h *NewIssueHandler) Handle(ctx context.Context, content *MailContent, doer *user_model.User, payload []byte) error {
|
||||
if doer == nil {
|
||||
return util.NewInvalidArgumentErrorf("doer can't be nil")
|
||||
}
|
||||
|
||||
ref, err := incoming_payload.GetReferenceFromPayload(ctx, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var repo *repo_model.Repository
|
||||
|
||||
switch r := ref.(type) {
|
||||
case *repo_model.Repository:
|
||||
repo = r
|
||||
default:
|
||||
return util.NewInvalidArgumentErrorf("unsupported reply reference: %v", ref)
|
||||
}
|
||||
|
||||
if util.IsEmptyString(content.Subject) {
|
||||
return nil
|
||||
}
|
||||
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, repo, doer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !perm.CanRead(unit.TypeIssues) {
|
||||
return nil
|
||||
}
|
||||
|
||||
attachmentIDs := make([]string, 0, len(content.Attachments))
|
||||
if setting.Attachment.Enabled {
|
||||
for _, attachment := range content.Attachments {
|
||||
a, err := attachment_service.UploadAttachment(ctx, bytes.NewReader(attachment.Content), setting.Attachment.AllowedTypes, int64(len(attachment.Content)), &repo_model.Attachment{
|
||||
Name: attachment.Name,
|
||||
UploaderID: doer.ID,
|
||||
RepoID: repo.ID,
|
||||
})
|
||||
if err != nil {
|
||||
if upload.IsErrFileTypeForbidden(err) {
|
||||
log.Info("NewIssueHandler: Skipping disallowed attachment type: %s", attachment.Name)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
attachmentIDs = append(attachmentIDs, a.UUID)
|
||||
}
|
||||
}
|
||||
|
||||
issue := &issues_model.Issue{
|
||||
RepoID: repo.ID,
|
||||
Repo: repo,
|
||||
Title: content.Subject,
|
||||
PosterID: doer.ID,
|
||||
Poster: doer,
|
||||
Content: content.Content,
|
||||
}
|
||||
|
||||
if err := issue_service.NewIssue(ctx, repo, issue, []int64{}, attachmentIDs, []int64{}, 0); err != nil {
|
||||
log.Warn("NewIssueHandler: Failed to create issue: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewPullRequest handles new pull requests
|
||||
type NewPullRequest struct{}
|
||||
|
||||
func (h *NewPullRequest) Handle(ctx context.Context, _ *MailContent, doer *user_model.User, payload []byte) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
38
services/mailer/incoming/mailto_new_issue.go
Normal file
38
services/mailer/incoming/mailto_new_issue.go
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package incoming
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
incoming_payload "code.gitea.io/gitea/services/mailer/incoming/payload"
|
||||
"code.gitea.io/gitea/services/mailer/token"
|
||||
)
|
||||
|
||||
func GenerateMailToRepoURL(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, event user_model.RepositoryRandsType) (string, string, error) {
|
||||
_, err := doer.GetOrCreateRandsForRepository(ctx, repo.ID, event)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
payload, err := incoming_payload.CreateReferencePayload(&incoming_payload.ReferenceRepository{
|
||||
RepositoryID: repo.ID,
|
||||
ActionType: incoming_payload.ReferenceRepositoryActionTypeNewIssue,
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
token, err := token.CreateToken(ctx, token.NewIssueHandlerType, doer, payload)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
mailToAddress := strings.Replace(setting.IncomingEmail.ReplyToAddress, setting.IncomingEmail.TokenPlaceholder, token, 1)
|
||||
return token, mailToAddress, nil
|
||||
}
|
@ -7,6 +7,8 @@ import (
|
||||
"context"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
@ -17,8 +19,22 @@ type payloadReferenceType byte
|
||||
const (
|
||||
payloadReferenceIssue payloadReferenceType = iota
|
||||
payloadReferenceComment
|
||||
payloadReferenceNewIssue
|
||||
payloadReferenceNewPullRequest
|
||||
)
|
||||
|
||||
type ReferenceRepositoryActionType int64
|
||||
|
||||
const (
|
||||
ReferenceRepositoryActionTypeNewIssue ReferenceRepositoryActionType = iota
|
||||
ReferenceRepositoryActionTypeNewPullRequest
|
||||
)
|
||||
|
||||
type ReferenceRepository struct {
|
||||
RepositoryID int64
|
||||
ActionType ReferenceRepositoryActionType
|
||||
}
|
||||
|
||||
// CreateReferencePayload creates data which GetReferenceFromPayload resolves to the reference again.
|
||||
func CreateReferencePayload(reference any) ([]byte, error) {
|
||||
var refType payloadReferenceType
|
||||
@ -31,6 +47,17 @@ func CreateReferencePayload(reference any) ([]byte, error) {
|
||||
case *issues_model.Comment:
|
||||
refType = payloadReferenceComment
|
||||
refID = r.ID
|
||||
case *ReferenceRepository:
|
||||
switch r.ActionType {
|
||||
case ReferenceRepositoryActionTypeNewIssue:
|
||||
refType = payloadReferenceNewIssue
|
||||
refID = r.RepositoryID
|
||||
case ReferenceRepositoryActionTypeNewPullRequest:
|
||||
refType = payloadReferenceNewPullRequest
|
||||
refID = r.RepositoryID
|
||||
default:
|
||||
return nil, util.NewInvalidArgumentErrorf("unsupported repository reference action type: %d", r.ActionType)
|
||||
}
|
||||
default:
|
||||
return nil, util.NewInvalidArgumentErrorf("unsupported reference type: %T", r)
|
||||
}
|
||||
@ -64,7 +91,41 @@ func GetReferenceFromPayload(ctx context.Context, payload []byte) (any, error) {
|
||||
return issues_model.GetIssueByID(ctx, id)
|
||||
case payloadReferenceComment:
|
||||
return issues_model.GetCommentByID(ctx, id)
|
||||
case payloadReferenceNewIssue:
|
||||
return repo_model.GetRepositoryByID(ctx, id)
|
||||
case payloadReferenceNewPullRequest:
|
||||
return repo_model.GetRepositoryByID(ctx, id)
|
||||
default:
|
||||
return nil, util.NewInvalidArgumentErrorf("unsupported reference type: %T", ref)
|
||||
}
|
||||
}
|
||||
|
||||
func GetRandsFromPayload(ctx context.Context, doer *user_model.User, payload []byte) []byte {
|
||||
if len(payload) < 1 {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
if payload[0] != replyPayloadVersion1 {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
var ref payloadReferenceType
|
||||
var id int64
|
||||
if err := util.UnpackData(payload[1:], &ref, &id); err != nil {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
switch ref {
|
||||
case payloadReferenceIssue:
|
||||
return []byte(doer.Rands)
|
||||
case payloadReferenceComment:
|
||||
return []byte(doer.Rands)
|
||||
case payloadReferenceNewIssue:
|
||||
rands, _ := user_model.GetRandsForRepository(ctx, doer.ID, id, user_model.RepositoryRandsTypeNewIssue)
|
||||
return []byte(rands)
|
||||
case payloadReferenceNewPullRequest:
|
||||
return []byte{}
|
||||
default:
|
||||
return []byte{}
|
||||
}
|
||||
}
|
||||
|
@ -325,7 +325,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
|
||||
|
||||
if setting.IncomingEmail.Enabled {
|
||||
if replyPayload != nil {
|
||||
token, err := token.CreateToken(token.ReplyHandlerType, recipient, replyPayload)
|
||||
token, err := token.CreateToken(ctx, token.ReplyHandlerType, recipient, replyPayload)
|
||||
if err != nil {
|
||||
log.Error("CreateToken failed: %v", err)
|
||||
} else {
|
||||
@ -337,7 +337,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
|
||||
}
|
||||
}
|
||||
|
||||
token, err := token.CreateToken(token.UnsubscribeHandlerType, recipient, unsubscribePayload)
|
||||
token, err := token.CreateToken(ctx, token.UnsubscribeHandlerType, recipient, unsubscribePayload)
|
||||
if err != nil {
|
||||
log.Error("CreateToken failed: %v", err)
|
||||
} else {
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
incoming_payload "code.gitea.io/gitea/services/mailer/incoming/payload"
|
||||
)
|
||||
|
||||
// A token is a verifiable container describing an action.
|
||||
@ -34,6 +35,8 @@ const (
|
||||
UnknownHandlerType HandlerType = iota
|
||||
ReplyHandlerType
|
||||
UnsubscribeHandlerType
|
||||
NewIssueHandlerType
|
||||
NewPullRequestHandlerType
|
||||
)
|
||||
|
||||
var encodingWithoutPadding = base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||
@ -51,7 +54,7 @@ func (err *ErrToken) Unwrap() error {
|
||||
}
|
||||
|
||||
// CreateToken creates a token for the action/user tuple
|
||||
func CreateToken(ht HandlerType, user *user_model.User, data []byte) (string, error) {
|
||||
func CreateToken(ctx context.Context, ht HandlerType, user *user_model.User, data []byte) (string, error) {
|
||||
payload, err := util.PackData(
|
||||
time.Now().AddDate(tokenLifetimeInYears, 0, 0).Unix(),
|
||||
ht,
|
||||
@ -63,7 +66,7 @@ func CreateToken(ht HandlerType, user *user_model.User, data []byte) (string, er
|
||||
|
||||
packagedData, err := util.PackData(
|
||||
user.ID,
|
||||
generateHmac([]byte(user.Rands), payload),
|
||||
generateHmac(incoming_payload.GetRandsFromPayload(ctx, user, data), payload),
|
||||
payload,
|
||||
)
|
||||
if err != nil {
|
||||
@ -100,10 +103,6 @@ func ExtractToken(ctx context.Context, token string) (HandlerType, *user_model.U
|
||||
return UnknownHandlerType, nil, nil, err
|
||||
}
|
||||
|
||||
if !crypto_hmac.Equal(hmac, generateHmac([]byte(user.Rands), payload)) {
|
||||
return UnknownHandlerType, nil, nil, &ErrToken{"verification failed"}
|
||||
}
|
||||
|
||||
var expiresUnix int64
|
||||
var handlerType HandlerType
|
||||
var innerPayload []byte
|
||||
@ -111,6 +110,10 @@ func ExtractToken(ctx context.Context, token string) (HandlerType, *user_model.U
|
||||
return UnknownHandlerType, nil, nil, err
|
||||
}
|
||||
|
||||
if !crypto_hmac.Equal(hmac, generateHmac(incoming_payload.GetRandsFromPayload(ctx, user, innerPayload), payload)) {
|
||||
return UnknownHandlerType, nil, nil, &ErrToken{"verification failed"}
|
||||
}
|
||||
|
||||
if time.Unix(expiresUnix, 0).Before(time.Now()) {
|
||||
return UnknownHandlerType, nil, nil, &ErrToken{"token expired"}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ func GetCommitGraph(r *git.Repository, page, maxAllowedColors int, hidePRRefs bo
|
||||
}
|
||||
|
||||
if len(branches) == 0 {
|
||||
graphCmd.AddArguments("--all")
|
||||
graphCmd.AddArguments("--tags", "--branches")
|
||||
}
|
||||
|
||||
graphCmd.AddArguments("-C", "-M", "--date=iso-strict").
|
||||
|
@ -50,6 +50,9 @@
|
||||
</div>
|
||||
</div>
|
||||
{{template "shared/issuelist" dict "." . "listType" "repo"}}
|
||||
{{if and .PageIsIssueList .MailToIssueEnabled}}
|
||||
{{template "repo/issue/mailto_module" dict "." .}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
|
24
templates/repo/issue/mailto_module.tmpl
Normal file
24
templates/repo/issue/mailto_module.tmpl
Normal file
@ -0,0 +1,24 @@
|
||||
<div class="tw-flex tw-justify-center tw-mb-4">
|
||||
<div class="ui small modal get-mailto-addr" id="get-mailto-addr" data-reset-url="{{.MailToIssueTokenResetUrl}}">
|
||||
<div class="header">{{ctx.Locale.Tr "repo.issues.mailto_modal.title"}}</div>
|
||||
<div class="content tw-flex tw-flex-col">
|
||||
<div>{{ctx.Locale.Tr "repo.issues.mailto_modal.desc_1"}}</div>
|
||||
<div class="ui action input mailto-buttons-combo tw-p-2">
|
||||
<input size="60" class="repo-mailto-url" value="{{.MailToIssueAddress}}" readonly>
|
||||
<button class="ui small icon button" data-clipboard-target=".repo-mailto-url" data-tooltip-content="{{ctx.Locale.Tr "copy_url"}}">
|
||||
{{svg "octicon-copy" 14}}
|
||||
</button>
|
||||
<a data-tooltip-content="{{ctx.Locale.Tr "repo.issues.mailto_modal.send_mail"}}" class="ui small icon button send-mail-link" href="{{.MailToIssueLink}}">
|
||||
{{svg "octicon-mail"}}
|
||||
</a>
|
||||
</div>
|
||||
<div>{{ctx.Locale.Tr "repo.issues.mailto_modal.desc_2"}}</div>
|
||||
<div>{{ctx.Locale.Tr "repo.issues.mailto_modal.desc_3" "reset-get-mailto-addr"}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tw-justify-center">
|
||||
<button class="btn show-modal show-get-mailto-addr" data-modal="#get-mailto-addr">
|
||||
{{ctx.Locale.Tr "repo.issues.mailto_modal.mailto_link"}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
@ -63,7 +63,7 @@ func TestIncomingEmail(t *testing.T) {
|
||||
|
||||
payload := []byte{1, 2, 3, 4, 5}
|
||||
|
||||
token, err := token_service.CreateToken(token_service.ReplyHandlerType, user, payload)
|
||||
token, err := token_service.CreateToken(db.DefaultContext, token_service.ReplyHandlerType, user, payload)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
@ -186,7 +186,7 @@ func TestIncomingEmail(t *testing.T) {
|
||||
|
||||
payload, err := incoming_payload.CreateReferencePayload(issue)
|
||||
assert.NoError(t, err)
|
||||
token, err := token_service.CreateToken(token_service.ReplyHandlerType, user, payload)
|
||||
token, err := token_service.CreateToken(db.DefaultContext, token_service.ReplyHandlerType, user, payload)
|
||||
assert.NoError(t, err)
|
||||
|
||||
msg := sender_service.NewMessageFrom(
|
||||
|
@ -223,6 +223,31 @@ async function initIssuePinSort() {
|
||||
});
|
||||
}
|
||||
|
||||
function initGetMailToAddrModal() {
|
||||
const modal = document.querySelector('.modal.get-mailto-addr');
|
||||
if (modal === null) return;
|
||||
|
||||
const url = modal.getAttribute('data-reset-url');
|
||||
|
||||
const input = modal.querySelector<HTMLInputElement>('.repo-mailto-url');
|
||||
const buttonReset = modal.querySelector<HTMLAnchorElement>('.reset-get-mailto-addr');
|
||||
const sendMailLink = modal.querySelector<HTMLAnchorElement>('.send-mail-link');
|
||||
|
||||
buttonReset.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const rsp = await POST(url);
|
||||
if (rsp.status !== 200) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await rsp.json();
|
||||
|
||||
input.value = data.url;
|
||||
sendMailLink.href = `mailto:${data.url}`;
|
||||
});
|
||||
}
|
||||
|
||||
export function initRepoIssueList() {
|
||||
if (document.querySelector('.page-content.repository.issue-list, .page-content.repository.milestone-issue-list')) {
|
||||
initRepoIssueListCheckboxes();
|
||||
@ -232,4 +257,5 @@ export function initRepoIssueList() {
|
||||
// user or org home: issue list, pull request list
|
||||
queryElems(document, '.ui.dropdown.user-remote-search', (el) => initDropdownUserRemoteSearch(el));
|
||||
}
|
||||
initGetMailToAddrModal();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user