chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/apache/answer/internal/entity"
|
||||
"github.com/apache/answer/internal/schema"
|
||||
"github.com/apache/answer/pkg/token"
|
||||
"github.com/apache/answer/plugin"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
// CaptchaRepo captcha repository
|
||||
type CaptchaRepo interface {
|
||||
SetCaptcha(ctx context.Context, key, captcha string) (err error)
|
||||
GetCaptcha(ctx context.Context, key string) (captcha string, err error)
|
||||
DelCaptcha(ctx context.Context, key string) (err error)
|
||||
SetActionType(ctx context.Context, unit, actionType, config string, amount int) (err error)
|
||||
GetActionType(ctx context.Context, unit, actionType string) (actioninfo *entity.ActionRecordInfo, err error)
|
||||
DelActionType(ctx context.Context, unit, actionType string) (err error)
|
||||
}
|
||||
|
||||
// CaptchaService kit service
|
||||
type CaptchaService struct {
|
||||
captchaRepo CaptchaRepo
|
||||
}
|
||||
|
||||
// NewCaptchaService captcha service
|
||||
func NewCaptchaService(captchaRepo CaptchaRepo) *CaptchaService {
|
||||
return &CaptchaService{
|
||||
captchaRepo: captchaRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// ActionRecord action record
|
||||
func (cs *CaptchaService) ActionRecord(ctx context.Context, req *schema.ActionRecordReq) (resp *schema.ActionRecordResp, err error) {
|
||||
resp = &schema.ActionRecordResp{}
|
||||
unit := req.IP
|
||||
switch req.Action {
|
||||
case entity.CaptchaActionEditUserinfo:
|
||||
unit = req.UserID
|
||||
case entity.CaptchaActionQuestion:
|
||||
unit = req.UserID
|
||||
case entity.CaptchaActionAnswer:
|
||||
unit = req.UserID
|
||||
case entity.CaptchaActionComment:
|
||||
unit = req.UserID
|
||||
case entity.CaptchaActionEdit:
|
||||
unit = req.UserID
|
||||
case entity.CaptchaActionInvitationAnswer:
|
||||
unit = req.UserID
|
||||
case entity.CaptchaActionSearch:
|
||||
if req.UserID != "" {
|
||||
unit = req.UserID
|
||||
}
|
||||
case entity.CaptchaActionReport:
|
||||
unit = req.UserID
|
||||
case entity.CaptchaActionDelete:
|
||||
unit = req.UserID
|
||||
case entity.CaptchaActionVote:
|
||||
unit = req.UserID
|
||||
}
|
||||
verificationResult := cs.ValidationStrategy(ctx, unit, req.Action)
|
||||
if !verificationResult {
|
||||
resp.Verify = true
|
||||
resp.CaptchaID, resp.CaptchaImg, err = cs.GenerateCaptcha(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("GenerateCaptcha error: %v", err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ActionRecordVerifyCaptcha
|
||||
// Verify that you need to enter a CAPTCHA, and that the CAPTCHA is correct
|
||||
func (cs *CaptchaService) ActionRecordVerifyCaptcha(
|
||||
ctx context.Context, actionType string, unit string, captchaID string, captchaCode string,
|
||||
) bool {
|
||||
verificationResult := cs.ValidationStrategy(ctx, unit, actionType)
|
||||
if verificationResult {
|
||||
return true
|
||||
}
|
||||
pass, err := cs.VerifyCaptcha(ctx, captchaID, captchaCode)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return pass
|
||||
}
|
||||
|
||||
func (cs *CaptchaService) ActionRecordAdd(ctx context.Context, actionType string, unit string) {
|
||||
info, err := cs.captchaRepo.GetActionType(ctx, unit, actionType)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
amount := 1
|
||||
if info != nil {
|
||||
amount = info.Num + 1
|
||||
}
|
||||
err = cs.captchaRepo.SetActionType(ctx, unit, actionType, "", amount)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *CaptchaService) ActionRecordDel(ctx context.Context, actionType string, unit string) {
|
||||
err := cs.captchaRepo.DelActionType(ctx, unit, actionType)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateCaptcha generate captcha
|
||||
func (cs *CaptchaService) GenerateCaptcha(ctx context.Context) (key, captchaBase64 string, err error) {
|
||||
realCaptcha := ""
|
||||
key = token.GenerateToken()
|
||||
_ = plugin.CallCaptcha(func(fn plugin.Captcha) error {
|
||||
if captcha, code := fn.Create(); len(code) > 0 {
|
||||
captchaBase64 = captcha
|
||||
realCaptcha = code
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if len(realCaptcha) == 0 {
|
||||
return key, captchaBase64, nil
|
||||
}
|
||||
|
||||
err = cs.captchaRepo.SetCaptcha(ctx, key, realCaptcha)
|
||||
return key, captchaBase64, err
|
||||
}
|
||||
|
||||
// VerifyCaptcha generate captcha
|
||||
func (cs *CaptchaService) VerifyCaptcha(ctx context.Context, key, captcha string) (isCorrect bool, err error) {
|
||||
realCaptcha, _ := cs.captchaRepo.GetCaptcha(ctx, key)
|
||||
|
||||
_ = plugin.CallCaptcha(func(fn plugin.Captcha) error {
|
||||
isCorrect = fn.Verify(realCaptcha, captcha)
|
||||
return nil
|
||||
})
|
||||
|
||||
_ = cs.captchaRepo.DelCaptcha(ctx, key)
|
||||
return isCorrect, nil
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package action
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/apache/answer/plugin"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
|
||||
"github.com/apache/answer/internal/entity"
|
||||
)
|
||||
|
||||
// ValidationStrategy
|
||||
// true pass
|
||||
// false need captcha
|
||||
func (cs *CaptchaService) ValidationStrategy(ctx context.Context, unit, actionType string) bool {
|
||||
// If the captcha is not enabled, the verification is passed directly
|
||||
if !plugin.CaptchaEnabled() {
|
||||
return true
|
||||
}
|
||||
info, err := cs.captchaRepo.GetActionType(ctx, unit, actionType)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return false
|
||||
}
|
||||
switch actionType {
|
||||
case entity.CaptchaActionEmail:
|
||||
return cs.CaptchaActionEmail(ctx, unit, info)
|
||||
case entity.CaptchaActionPassword:
|
||||
return cs.CaptchaActionPassword(ctx, unit, info)
|
||||
case entity.CaptchaActionEditUserinfo:
|
||||
return cs.CaptchaActionEditUserinfo(ctx, unit, info)
|
||||
case entity.CaptchaActionQuestion:
|
||||
return cs.CaptchaActionQuestion(ctx, unit, info)
|
||||
case entity.CaptchaActionAnswer:
|
||||
return cs.CaptchaActionAnswer(ctx, unit, info)
|
||||
case entity.CaptchaActionComment:
|
||||
return cs.CaptchaActionComment(ctx, unit, info)
|
||||
case entity.CaptchaActionEdit:
|
||||
return cs.CaptchaActionEdit(ctx, unit, info)
|
||||
case entity.CaptchaActionInvitationAnswer:
|
||||
return cs.CaptchaActionInvitationAnswer(ctx, unit, info)
|
||||
case entity.CaptchaActionSearch:
|
||||
return cs.CaptchaActionSearch(ctx, unit, info)
|
||||
case entity.CaptchaActionReport:
|
||||
return cs.CaptchaActionReport(ctx, unit, info)
|
||||
case entity.CaptchaActionDelete:
|
||||
return cs.CaptchaActionDelete(ctx, unit, info)
|
||||
case entity.CaptchaActionVote:
|
||||
return cs.CaptchaActionVote(ctx, unit, info)
|
||||
}
|
||||
// actionType not found
|
||||
return false
|
||||
}
|
||||
|
||||
func (cs *CaptchaService) CaptchaActionEmail(ctx context.Context, unit string, actionInfo *entity.ActionRecordInfo) bool {
|
||||
// You need a verification code every time
|
||||
return false
|
||||
}
|
||||
|
||||
func (cs *CaptchaService) CaptchaActionPassword(ctx context.Context, unit string, actionInfo *entity.ActionRecordInfo) bool {
|
||||
if actionInfo == nil {
|
||||
return true
|
||||
}
|
||||
setNum := 3
|
||||
setTime := int64(60 * 30) // seconds
|
||||
now := time.Now().Unix()
|
||||
if now-actionInfo.LastTime <= setTime && actionInfo.Num >= setNum {
|
||||
return false
|
||||
}
|
||||
if now-actionInfo.LastTime != 0 && now-actionInfo.LastTime > setTime {
|
||||
if err := cs.captchaRepo.SetActionType(ctx, unit, entity.CaptchaActionPassword, "", 0); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (cs *CaptchaService) CaptchaActionEditUserinfo(ctx context.Context, unit string, actionInfo *entity.ActionRecordInfo) bool {
|
||||
if actionInfo == nil {
|
||||
return true
|
||||
}
|
||||
setNum := 3
|
||||
setTime := int64(60 * 30) // seconds
|
||||
now := time.Now().Unix()
|
||||
if now-actionInfo.LastTime <= setTime && actionInfo.Num >= setNum {
|
||||
return false
|
||||
}
|
||||
if now-actionInfo.LastTime != 0 && now-actionInfo.LastTime > setTime {
|
||||
if err := cs.captchaRepo.SetActionType(ctx, unit, entity.CaptchaActionEditUserinfo, "", 0); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (cs *CaptchaService) CaptchaActionQuestion(ctx context.Context, unit string, actionInfo *entity.ActionRecordInfo) bool {
|
||||
if actionInfo == nil {
|
||||
return true
|
||||
}
|
||||
setNum := 10
|
||||
setTime := int64(5) // seconds
|
||||
now := time.Now().Unix()
|
||||
if now-actionInfo.LastTime <= setTime || actionInfo.Num >= setNum {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (cs *CaptchaService) CaptchaActionAnswer(ctx context.Context, unit string, actionInfo *entity.ActionRecordInfo) bool {
|
||||
if actionInfo == nil {
|
||||
return true
|
||||
}
|
||||
setNum := 10
|
||||
setTime := int64(5) // seconds
|
||||
now := time.Now().Unix()
|
||||
if now-actionInfo.LastTime <= setTime || actionInfo.Num >= setNum {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (cs *CaptchaService) CaptchaActionComment(ctx context.Context, unit string, actionInfo *entity.ActionRecordInfo) bool {
|
||||
if actionInfo == nil {
|
||||
return true
|
||||
}
|
||||
setNum := 30
|
||||
setTime := int64(1) // seconds
|
||||
now := time.Now().Unix()
|
||||
if now-actionInfo.LastTime <= setTime || actionInfo.Num >= setNum {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (cs *CaptchaService) CaptchaActionEdit(ctx context.Context, unit string, actionInfo *entity.ActionRecordInfo) bool {
|
||||
if actionInfo == nil {
|
||||
return true
|
||||
}
|
||||
setNum := 10
|
||||
return actionInfo.Num < setNum
|
||||
}
|
||||
|
||||
func (cs *CaptchaService) CaptchaActionInvitationAnswer(ctx context.Context, unit string, actionInfo *entity.ActionRecordInfo) bool {
|
||||
if actionInfo == nil {
|
||||
return true
|
||||
}
|
||||
setNum := 30
|
||||
return actionInfo.Num < setNum
|
||||
}
|
||||
|
||||
func (cs *CaptchaService) CaptchaActionSearch(ctx context.Context, unit string, actionInfo *entity.ActionRecordInfo) bool {
|
||||
if actionInfo == nil {
|
||||
return true
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
setNum := 20
|
||||
setTime := int64(60) // seconds
|
||||
if now-actionInfo.LastTime <= setTime && actionInfo.Num >= setNum {
|
||||
return false
|
||||
}
|
||||
if now-actionInfo.LastTime > setTime {
|
||||
if err := cs.captchaRepo.SetActionType(ctx, unit, entity.CaptchaActionSearch, "", 0); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (cs *CaptchaService) CaptchaActionReport(ctx context.Context, unit string, actionInfo *entity.ActionRecordInfo) bool {
|
||||
if actionInfo == nil {
|
||||
return true
|
||||
}
|
||||
setNum := 30
|
||||
setTime := int64(1) // seconds
|
||||
now := time.Now().Unix()
|
||||
if now-actionInfo.LastTime <= setTime || actionInfo.Num >= setNum {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (cs *CaptchaService) CaptchaActionDelete(ctx context.Context, unit string, actionInfo *entity.ActionRecordInfo) bool {
|
||||
if actionInfo == nil {
|
||||
return true
|
||||
}
|
||||
setNum := 5
|
||||
setTime := int64(5) // seconds
|
||||
now := time.Now().Unix()
|
||||
if now-actionInfo.LastTime <= setTime || actionInfo.Num >= setNum {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (cs *CaptchaService) CaptchaActionVote(ctx context.Context, unit string, actionInfo *entity.ActionRecordInfo) bool {
|
||||
if actionInfo == nil {
|
||||
return true
|
||||
}
|
||||
setNum := 40
|
||||
return actionInfo.Num < setNum
|
||||
}
|
||||
Reference in New Issue
Block a user