mirror of
https://github.com/TeaOSLab/EdgeAPI.git
synced 2025-11-08 19:40:24 +08:00
HTTP Header:实现请求方法、域名、状态码等限制,实现内容替换功能
This commit is contained in:
@@ -2,7 +2,7 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"github.com/TeaOSLab/EdgeAPI/internal/errors"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
"github.com/iwind/TeaGo/Tea"
|
"github.com/iwind/TeaGo/Tea"
|
||||||
@@ -80,22 +80,69 @@ func (this *HTTPHeaderDAO) FindHTTPHeaderName(tx *dbs.Tx, id int64) (string, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateHeader 创建Header
|
// CreateHeader 创建Header
|
||||||
func (this *HTTPHeaderDAO) CreateHeader(tx *dbs.Tx, name string, value string) (int64, error) {
|
func (this *HTTPHeaderDAO) CreateHeader(tx *dbs.Tx, userId int64, name string, value string, status []int, disableRedirect bool, shouldAppend bool, shouldReplace bool, replaceValues []*shared.HTTPHeaderReplaceValue, methods []string, domains []string) (int64, error) {
|
||||||
op := NewHTTPHeaderOperator()
|
op := NewHTTPHeaderOperator()
|
||||||
|
op.UserId = userId
|
||||||
op.State = HTTPHeaderStateEnabled
|
op.State = HTTPHeaderStateEnabled
|
||||||
op.IsOn = true
|
op.IsOn = true
|
||||||
op.Name = name
|
op.Name = name
|
||||||
op.Value = value
|
op.Value = value
|
||||||
|
|
||||||
statusConfig := &shared.HTTPStatusConfig{
|
// status
|
||||||
|
var statusConfig *shared.HTTPStatusConfig
|
||||||
|
if len(status) == 0 {
|
||||||
|
statusConfig = &shared.HTTPStatusConfig{
|
||||||
Always: true,
|
Always: true,
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
statusConfig = &shared.HTTPStatusConfig{
|
||||||
|
Always: false,
|
||||||
|
Codes: status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
statusJSON, err := json.Marshal(statusConfig)
|
statusJSON, err := json.Marshal(statusConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
op.Status = statusJSON
|
op.Status = statusJSON
|
||||||
|
|
||||||
|
op.DisableRedirect = disableRedirect
|
||||||
|
op.ShouldAppend = shouldAppend
|
||||||
|
op.ShouldReplace = shouldReplace
|
||||||
|
|
||||||
|
if len(replaceValues) == 0 {
|
||||||
|
op.ReplaceValues = "[]"
|
||||||
|
} else {
|
||||||
|
replaceValuesJSON, err := json.Marshal(replaceValues)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
op.ReplaceValues = replaceValuesJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
// methods
|
||||||
|
if len(methods) == 0 {
|
||||||
|
op.Methods = "[]"
|
||||||
|
} else {
|
||||||
|
methodsJSON, err := json.Marshal(methods)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
op.Methods = methodsJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
// domains
|
||||||
|
if len(domains) == 0 {
|
||||||
|
op.Domains = "[]"
|
||||||
|
} else {
|
||||||
|
domainsJSON, err := json.Marshal(domains)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
op.Domains = domainsJSON
|
||||||
|
}
|
||||||
|
|
||||||
err = this.Save(tx, op)
|
err = this.Save(tx, op)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@@ -104,7 +151,7 @@ func (this *HTTPHeaderDAO) CreateHeader(tx *dbs.Tx, name string, value string) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateHeader 修改Header
|
// UpdateHeader 修改Header
|
||||||
func (this *HTTPHeaderDAO) UpdateHeader(tx *dbs.Tx, headerId int64, name string, value string) error {
|
func (this *HTTPHeaderDAO) UpdateHeader(tx *dbs.Tx, headerId int64, name string, value string, status []int, disableRedirect bool, shouldAppend bool, shouldReplace bool, replaceValues []*shared.HTTPHeaderReplaceValue, methods []string, domains []string) error {
|
||||||
if headerId <= 0 {
|
if headerId <= 0 {
|
||||||
return errors.New("invalid headerId")
|
return errors.New("invalid headerId")
|
||||||
}
|
}
|
||||||
@@ -113,7 +160,63 @@ func (this *HTTPHeaderDAO) UpdateHeader(tx *dbs.Tx, headerId int64, name string,
|
|||||||
op.Id = headerId
|
op.Id = headerId
|
||||||
op.Name = name
|
op.Name = name
|
||||||
op.Value = value
|
op.Value = value
|
||||||
err := this.Save(tx, op)
|
|
||||||
|
// status
|
||||||
|
var statusConfig *shared.HTTPStatusConfig
|
||||||
|
if len(status) == 0 {
|
||||||
|
statusConfig = &shared.HTTPStatusConfig{
|
||||||
|
Always: true,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
statusConfig = &shared.HTTPStatusConfig{
|
||||||
|
Always: false,
|
||||||
|
Codes: status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
statusJSON, err := json.Marshal(statusConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
op.Status = statusJSON
|
||||||
|
|
||||||
|
op.DisableRedirect = disableRedirect
|
||||||
|
op.ShouldAppend = shouldAppend
|
||||||
|
op.ShouldReplace = shouldReplace
|
||||||
|
|
||||||
|
if len(replaceValues) == 0 {
|
||||||
|
op.ReplaceValues = "[]"
|
||||||
|
} else {
|
||||||
|
replaceValuesJSON, err := json.Marshal(replaceValues)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
op.ReplaceValues = replaceValuesJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
// methods
|
||||||
|
if len(methods) == 0 {
|
||||||
|
op.Methods = "[]"
|
||||||
|
} else {
|
||||||
|
methodsJSON, err := json.Marshal(methods)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
op.Methods = methodsJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
// domains
|
||||||
|
if len(domains) == 0 {
|
||||||
|
op.Domains = "[]"
|
||||||
|
} else {
|
||||||
|
domainsJSON, err := json.Marshal(domains)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
op.Domains = domainsJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
err = this.Save(tx, op)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -136,7 +239,21 @@ func (this *HTTPHeaderDAO) ComposeHeaderConfig(tx *dbs.Tx, headerId int64) (*sha
|
|||||||
config.IsOn = header.IsOn == 1
|
config.IsOn = header.IsOn == 1
|
||||||
config.Name = header.Name
|
config.Name = header.Name
|
||||||
config.Value = header.Value
|
config.Value = header.Value
|
||||||
|
config.DisableRedirect = header.DisableRedirect == 1
|
||||||
|
config.ShouldAppend = header.ShouldAppend == 1
|
||||||
|
|
||||||
|
// replace
|
||||||
|
config.ShouldReplace = header.ShouldReplace == 1
|
||||||
|
if len(header.ReplaceValues) > 0 {
|
||||||
|
var values = []*shared.HTTPHeaderReplaceValue{}
|
||||||
|
err = json.Unmarshal([]byte(header.ReplaceValues), &values)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.ReplaceValues = values
|
||||||
|
}
|
||||||
|
|
||||||
|
// status
|
||||||
if len(header.Status) > 0 {
|
if len(header.Status) > 0 {
|
||||||
status := &shared.HTTPStatusConfig{}
|
status := &shared.HTTPStatusConfig{}
|
||||||
err = json.Unmarshal([]byte(header.Status), status)
|
err = json.Unmarshal([]byte(header.Status), status)
|
||||||
@@ -146,6 +263,26 @@ func (this *HTTPHeaderDAO) ComposeHeaderConfig(tx *dbs.Tx, headerId int64) (*sha
|
|||||||
config.Status = status
|
config.Status = status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// methods
|
||||||
|
if len(header.Methods) > 0 {
|
||||||
|
var methods = []string{}
|
||||||
|
err = json.Unmarshal([]byte(header.Methods), &methods)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.Methods = methods
|
||||||
|
}
|
||||||
|
|
||||||
|
// domains
|
||||||
|
if len(header.Domains) > 0 {
|
||||||
|
var domains = []string{}
|
||||||
|
err = json.Unmarshal([]byte(header.Domains), &domains)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config.Domains = domains
|
||||||
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
// HTTP Header
|
// HTTPHeader HTTP Header
|
||||||
type HTTPHeader struct {
|
type HTTPHeader struct {
|
||||||
Id uint32 `field:"id"` // ID
|
Id uint32 `field:"id"` // ID
|
||||||
AdminId uint32 `field:"adminId"` // 管理员ID
|
AdminId uint32 `field:"adminId"` // 管理员ID
|
||||||
@@ -11,6 +11,12 @@ type HTTPHeader struct {
|
|||||||
Value string `field:"value"` // 值
|
Value string `field:"value"` // 值
|
||||||
Order uint32 `field:"order"` // 排序
|
Order uint32 `field:"order"` // 排序
|
||||||
Status string `field:"status"` // 状态码设置
|
Status string `field:"status"` // 状态码设置
|
||||||
|
DisableRedirect uint8 `field:"disableRedirect"` // 是否不支持跳转
|
||||||
|
ShouldAppend uint8 `field:"shouldAppend"` // 是否为附加
|
||||||
|
ShouldReplace uint8 `field:"shouldReplace"` // 是否替换变量
|
||||||
|
ReplaceValues string `field:"replaceValues"` // 替换的值
|
||||||
|
Methods string `field:"methods"` // 支持的方法
|
||||||
|
Domains string `field:"domains"` // 支持的域名
|
||||||
State uint8 `field:"state"` // 状态
|
State uint8 `field:"state"` // 状态
|
||||||
CreatedAt uint64 `field:"createdAt"` // 创建时间
|
CreatedAt uint64 `field:"createdAt"` // 创建时间
|
||||||
}
|
}
|
||||||
@@ -25,6 +31,12 @@ type HTTPHeaderOperator struct {
|
|||||||
Value interface{} // 值
|
Value interface{} // 值
|
||||||
Order interface{} // 排序
|
Order interface{} // 排序
|
||||||
Status interface{} // 状态码设置
|
Status interface{} // 状态码设置
|
||||||
|
DisableRedirect interface{} // 是否不支持跳转
|
||||||
|
ShouldAppend interface{} // 是否为附加
|
||||||
|
ShouldReplace interface{} // 是否替换变量
|
||||||
|
ReplaceValues interface{} // 替换的值
|
||||||
|
Methods interface{} // 支持的方法
|
||||||
|
Domains interface{} // 支持的域名
|
||||||
State interface{} // 状态
|
State interface{} // 状态
|
||||||
CreatedAt interface{} // 创建时间
|
CreatedAt interface{} // 创建时间
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,48 +186,6 @@ func (this *HTTPHeaderPolicyDAO) ComposeHeaderPolicyConfig(tx *dbs.Tx, headerPol
|
|||||||
config.Id = int64(policy.Id)
|
config.Id = int64(policy.Id)
|
||||||
config.IsOn = policy.IsOn == 1
|
config.IsOn = policy.IsOn == 1
|
||||||
|
|
||||||
// AddHeaders
|
|
||||||
if len(policy.AddHeaders) > 0 {
|
|
||||||
refs := []*shared.HTTPHeaderRef{}
|
|
||||||
err = json.Unmarshal([]byte(policy.AddHeaders), &refs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(refs) > 0 {
|
|
||||||
for _, ref := range refs {
|
|
||||||
headerConfig, err := SharedHTTPHeaderDAO.ComposeHeaderConfig(tx, ref.HeaderId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.AddHeaders = append(config.AddHeaders, headerConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddTrailers
|
|
||||||
if len(policy.AddTrailers) > 0 {
|
|
||||||
refs := []*shared.HTTPHeaderRef{}
|
|
||||||
err = json.Unmarshal([]byte(policy.AddTrailers), &refs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(refs) > 0 {
|
|
||||||
resultRefs := []*shared.HTTPHeaderRef{}
|
|
||||||
for _, ref := range refs {
|
|
||||||
headerConfig, err := SharedHTTPHeaderDAO.ComposeHeaderConfig(tx, ref.HeaderId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if headerConfig == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
resultRefs = append(resultRefs, ref)
|
|
||||||
config.AddTrailers = append(config.AddTrailers, headerConfig)
|
|
||||||
}
|
|
||||||
config.AddHeaderRefs = resultRefs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeaders
|
// SetHeaders
|
||||||
if len(policy.SetHeaders) > 0 {
|
if len(policy.SetHeaders) > 0 {
|
||||||
refs := []*shared.HTTPHeaderRef{}
|
refs := []*shared.HTTPHeaderRef{}
|
||||||
@@ -252,30 +210,6 @@ func (this *HTTPHeaderPolicyDAO) ComposeHeaderPolicyConfig(tx *dbs.Tx, headerPol
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplaceHeaders
|
|
||||||
if len(policy.ReplaceHeaders) > 0 {
|
|
||||||
refs := []*shared.HTTPHeaderRef{}
|
|
||||||
err = json.Unmarshal([]byte(policy.ReplaceHeaders), &refs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(refs) > 0 {
|
|
||||||
resultRefs := []*shared.HTTPHeaderRef{}
|
|
||||||
for _, ref := range refs {
|
|
||||||
headerConfig, err := SharedHTTPHeaderDAO.ComposeHeaderConfig(tx, ref.HeaderId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if headerConfig == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
resultRefs = append(resultRefs, ref)
|
|
||||||
config.ReplaceHeaders = append(config.ReplaceHeaders, headerConfig)
|
|
||||||
}
|
|
||||||
config.ReplaceHeaderRefs = resultRefs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete Headers
|
// Delete Headers
|
||||||
if len(policy.DeleteHeaders) > 0 {
|
if len(policy.DeleteHeaders) > 0 {
|
||||||
headers := []string{}
|
headers := []string{}
|
||||||
|
|||||||
@@ -4,14 +4,16 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/errors"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTTPHeaderService struct {
|
type HTTPHeaderService struct {
|
||||||
BaseService
|
BaseService
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建Header
|
// CreateHTTPHeader 创建Header
|
||||||
func (this *HTTPHeaderService) CreateHTTPHeader(ctx context.Context, req *pb.CreateHTTPHeaderRequest) (*pb.CreateHTTPHeaderResponse, error) {
|
func (this *HTTPHeaderService) CreateHTTPHeader(ctx context.Context, req *pb.CreateHTTPHeaderRequest) (*pb.CreateHTTPHeaderResponse, error) {
|
||||||
// 校验请求
|
// 校验请求
|
||||||
_, userId, err := this.ValidateAdminAndUser(ctx, 0, 0)
|
_, userId, err := this.ValidateAdminAndUser(ctx, 0, 0)
|
||||||
@@ -25,7 +27,22 @@ func (this *HTTPHeaderService) CreateHTTPHeader(ctx context.Context, req *pb.Cre
|
|||||||
|
|
||||||
tx := this.NullTx()
|
tx := this.NullTx()
|
||||||
|
|
||||||
headerId, err := models.SharedHTTPHeaderDAO.CreateHeader(tx, req.Name, req.Value)
|
// status
|
||||||
|
var newStatus = []int{}
|
||||||
|
for _, status := range req.Status {
|
||||||
|
newStatus = append(newStatus, int(status))
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace values
|
||||||
|
var replaceValues = []*shared.HTTPHeaderReplaceValue{}
|
||||||
|
if len(req.ReplaceValuesJSON) > 0 {
|
||||||
|
err = json.Unmarshal(req.ReplaceValuesJSON, &replaceValues)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("decode replace values failed: " + err.Error() + ", json: " + string(req.ReplaceValuesJSON))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headerId, err := models.SharedHTTPHeaderDAO.CreateHeader(tx, userId, req.Name, req.Value, newStatus, req.DisableRedirect, req.ShouldAppend, req.ShouldReplace, replaceValues, req.Methods, req.Domains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -33,7 +50,7 @@ func (this *HTTPHeaderService) CreateHTTPHeader(ctx context.Context, req *pb.Cre
|
|||||||
return &pb.CreateHTTPHeaderResponse{HeaderId: headerId}, nil
|
return &pb.CreateHTTPHeaderResponse{HeaderId: headerId}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修改Header
|
// UpdateHTTPHeader 修改Header
|
||||||
func (this *HTTPHeaderService) UpdateHTTPHeader(ctx context.Context, req *pb.UpdateHTTPHeaderRequest) (*pb.RPCSuccess, error) {
|
func (this *HTTPHeaderService) UpdateHTTPHeader(ctx context.Context, req *pb.UpdateHTTPHeaderRequest) (*pb.RPCSuccess, error) {
|
||||||
// 校验请求
|
// 校验请求
|
||||||
_, userId, err := this.ValidateAdminAndUser(ctx, 0, 0)
|
_, userId, err := this.ValidateAdminAndUser(ctx, 0, 0)
|
||||||
@@ -47,7 +64,22 @@ func (this *HTTPHeaderService) UpdateHTTPHeader(ctx context.Context, req *pb.Upd
|
|||||||
|
|
||||||
tx := this.NullTx()
|
tx := this.NullTx()
|
||||||
|
|
||||||
err = models.SharedHTTPHeaderDAO.UpdateHeader(tx, req.HeaderId, req.Name, req.Value)
|
// status
|
||||||
|
var newStatus = []int{}
|
||||||
|
for _, status := range req.Status {
|
||||||
|
newStatus = append(newStatus, int(status))
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace values
|
||||||
|
var replaceValues = []*shared.HTTPHeaderReplaceValue{}
|
||||||
|
if len(req.ReplaceValuesJSON) > 0 {
|
||||||
|
err = json.Unmarshal(req.ReplaceValuesJSON, &replaceValues)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("decode replace values failed: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = models.SharedHTTPHeaderDAO.UpdateHeader(tx, req.HeaderId, req.Name, req.Value, newStatus, req.DisableRedirect, req.ShouldAppend, req.ShouldReplace, replaceValues, req.Methods, req.Domains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -55,7 +87,7 @@ func (this *HTTPHeaderService) UpdateHTTPHeader(ctx context.Context, req *pb.Upd
|
|||||||
return this.Success()
|
return this.Success()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查找配置
|
// FindEnabledHTTPHeaderConfig 查找配置
|
||||||
func (this *HTTPHeaderService) FindEnabledHTTPHeaderConfig(ctx context.Context, req *pb.FindEnabledHTTPHeaderConfigRequest) (*pb.FindEnabledHTTPHeaderConfigResponse, error) {
|
func (this *HTTPHeaderService) FindEnabledHTTPHeaderConfig(ctx context.Context, req *pb.FindEnabledHTTPHeaderConfigRequest) (*pb.FindEnabledHTTPHeaderConfigResponse, error) {
|
||||||
// 校验请求
|
// 校验请求
|
||||||
_, userId, err := this.ValidateAdminAndUser(ctx, 0, 0)
|
_, userId, err := this.ValidateAdminAndUser(ctx, 0, 0)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user