mirror of
https://github.com/telanflow/mps.git
synced 2025-09-26 20:41:25 +08:00
mitm proxy examples
This commit is contained in:
@@ -113,6 +113,8 @@ func (ctx *Context) Next(req *http.Request) (*http.Response, error) {
|
|||||||
ctx.mi++
|
ctx.mi++
|
||||||
if ctx.mi >= total {
|
if ctx.mi >= total {
|
||||||
ctx.mi = -1
|
ctx.mi = -1
|
||||||
|
// Final request coverage
|
||||||
|
ctx.Request = req
|
||||||
// To make the middleware available to the tunnel proxy,
|
// To make the middleware available to the tunnel proxy,
|
||||||
// no response is obtained when the request method is equal to Connect
|
// no response is obtained when the request method is equal to Connect
|
||||||
if req.Method == http.MethodConnect {
|
if req.Method == http.MethodConnect {
|
||||||
|
69
examples/mitm-proxy/main.go
Normal file
69
examples/mitm-proxy/main.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/telanflow/mps"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"regexp"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A simple mitm proxy server
|
||||||
|
func main() {
|
||||||
|
quitSignChan := make(chan os.Signal)
|
||||||
|
|
||||||
|
// create proxy server
|
||||||
|
proxy := mps.NewHttpProxy()
|
||||||
|
|
||||||
|
// The Connect request is processed using MitmHandler
|
||||||
|
proxy.HandleConnect = mps.NewMitmHandlerWithContext(proxy.Ctx)
|
||||||
|
|
||||||
|
// Middleware
|
||||||
|
proxy.UseFunc(func(req *http.Request, ctx *mps.Context) (*http.Response, error) {
|
||||||
|
log.Printf("[INFO] middleware -- %s %s", req.Method, req.URL)
|
||||||
|
return ctx.Next(req)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Filter
|
||||||
|
reqGroup := proxy.OnRequest(mps.FilterHostMatches(regexp.MustCompile("^.*$")))
|
||||||
|
reqGroup.DoFunc(func(req *http.Request, ctx *mps.Context) (*http.Request, *http.Response) {
|
||||||
|
log.Printf("[INFO] req -- %s %s", req.Method, req.URL)
|
||||||
|
return req, nil
|
||||||
|
})
|
||||||
|
respGroup := proxy.OnResponse()
|
||||||
|
respGroup.DoFunc(func(resp *http.Response, err error, ctx *mps.Context) (*http.Response, error) {
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERRO] resp -- %s %v", ctx.Request.Method, err)
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] resp -- %d", resp.StatusCode)
|
||||||
|
return resp, err
|
||||||
|
})
|
||||||
|
|
||||||
|
// Started proxy server
|
||||||
|
srv := http.Server{
|
||||||
|
Addr: "localhost:8080",
|
||||||
|
Handler: proxy,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
log.Printf("MitmProxy started listen: http://%s", srv.Addr)
|
||||||
|
err := srv.ListenAndServe()
|
||||||
|
if errors.Is(err, http.ErrServerClosed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
quitSignChan <- syscall.SIGKILL
|
||||||
|
log.Fatalf("MitmProxy start fail: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// quit signal
|
||||||
|
signal.Notify(quitSignChan, syscall.SIGINT, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGQUIT)
|
||||||
|
|
||||||
|
<-quitSignChan
|
||||||
|
_ = srv.Close()
|
||||||
|
log.Fatal("MitmProxy server stop!")
|
||||||
|
}
|
@@ -14,23 +14,23 @@ import (
|
|||||||
|
|
||||||
// A simple reverse proxy server
|
// A simple reverse proxy server
|
||||||
func main() {
|
func main() {
|
||||||
targetHost, _ := url.Parse("https://www.google.com")
|
targetURL, _ := url.Parse("https://www.google.com")
|
||||||
quitSignChan := make(chan os.Signal)
|
quitSignChan := make(chan os.Signal)
|
||||||
|
|
||||||
// reverse proxy server
|
// reverse proxy server
|
||||||
proxy := mps.NewReverseHandler()
|
proxy := mps.NewReverseHandler()
|
||||||
proxy.UseFunc(middleware.SingleHostReverseProxy(targetHost))
|
proxy.UseFunc(middleware.SingleHostReverseProxy(targetURL))
|
||||||
|
|
||||||
reqGroup := proxy.OnRequest()
|
reqGroup := proxy.OnRequest()
|
||||||
reqGroup.DoFunc(func(req *http.Request, ctx *mps.Context) (*http.Request, *http.Response) {
|
reqGroup.DoFunc(func(req *http.Request, ctx *mps.Context) (*http.Request, *http.Response) {
|
||||||
log.Printf("[INFO] req -- %s", req.Host)
|
log.Printf("[INFO] req -- %s %s", req.Method, req.Host)
|
||||||
return req, nil
|
return req, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
respGroup := proxy.OnResponse()
|
respGroup := proxy.OnResponse()
|
||||||
respGroup.DoFunc(func(resp *http.Response, err error, ctx *mps.Context) (*http.Response, error) {
|
respGroup.DoFunc(func(resp *http.Response, err error, ctx *mps.Context) (*http.Response, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERRO] resp -- %v", err)
|
log.Printf("[ERRO] resp -- %s %v", ctx.Request.Method, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log.Printf("[INFO] resp -- %d", resp.StatusCode)
|
log.Printf("[INFO] resp -- %d", resp.StatusCode)
|
||||||
|
@@ -18,30 +18,30 @@ func main() {
|
|||||||
// create a http proxy server
|
// create a http proxy server
|
||||||
proxy := mps.NewHttpProxy()
|
proxy := mps.NewHttpProxy()
|
||||||
proxy.UseFunc(func(req *http.Request, ctx *mps.Context) (*http.Response, error) {
|
proxy.UseFunc(func(req *http.Request, ctx *mps.Context) (*http.Response, error) {
|
||||||
log.Printf("[INFO] middleware -- %s\n", req.URL)
|
log.Printf("[INFO] middleware -- %s %s", req.Method, req.URL)
|
||||||
return ctx.Next(req)
|
return ctx.Next(req)
|
||||||
})
|
})
|
||||||
|
|
||||||
reqGroup := proxy.OnRequest(mps.FilterHostMatches(regexp.MustCompile("^.*$")))
|
reqGroup := proxy.OnRequest(mps.FilterHostMatches(regexp.MustCompile("^.*$")))
|
||||||
reqGroup.DoFunc(func(req *http.Request, ctx *mps.Context) (*http.Request, *http.Response) {
|
reqGroup.DoFunc(func(req *http.Request, ctx *mps.Context) (*http.Request, *http.Response) {
|
||||||
log.Printf("[INFO] req -- %s\n", req.URL)
|
log.Printf("[INFO] req -- %s %s", req.Method, req.URL)
|
||||||
return req, nil
|
return req, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
respGroup := proxy.OnResponse()
|
respGroup := proxy.OnResponse()
|
||||||
respGroup.DoFunc(func(resp *http.Response, err error, ctx *mps.Context) (*http.Response, error) {
|
respGroup.DoFunc(func(resp *http.Response, err error, ctx *mps.Context) (*http.Response, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERRO] resp -- %v\n", err)
|
log.Printf("[ERRO] resp -- %s %v", ctx.Request.Method, err)
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[INFO] resp -- %d\n", resp.StatusCode)
|
log.Printf("[INFO] resp -- %d", resp.StatusCode)
|
||||||
return resp, err
|
return resp, err
|
||||||
})
|
})
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: "127.0.0.1:8081",
|
Addr: "localhost:8080",
|
||||||
Handler: proxy,
|
Handler: proxy,
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
|
58
filter_group.go
Normal file
58
filter_group.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package mps
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type FilterGroup interface {
|
||||||
|
Handle()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqCondition is a request filter group
|
||||||
|
type ReqFilterGroup struct {
|
||||||
|
ctx *Context
|
||||||
|
filters []Filter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cond *ReqFilterGroup) DoFunc(fn func(req *http.Request, ctx *Context) (*http.Request, *http.Response)) {
|
||||||
|
cond.Do(RequestHandleFunc(fn))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cond *ReqFilterGroup) Do(h RequestHandle) {
|
||||||
|
cond.ctx.UseFunc(func(req *http.Request, ctx *Context) (*http.Response, error) {
|
||||||
|
total := len(cond.filters)
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
if !cond.filters[i].Match(req) {
|
||||||
|
return ctx.Next(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req, resp := h.HandleRequest(req, ctx)
|
||||||
|
if resp != nil {
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Next(req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReqCondition is a response filter group
|
||||||
|
type RespFilterGroup struct {
|
||||||
|
ctx *Context
|
||||||
|
filters []Filter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cond *RespFilterGroup) DoFunc(fn func(resp *http.Response, err error, ctx *Context) (*http.Response, error)) {
|
||||||
|
cond.Do(ResponseHandleFunc(fn))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cond *RespFilterGroup) Do(h ResponseHandle) {
|
||||||
|
cond.ctx.UseFunc(func(req *http.Request, ctx *Context) (*http.Response, error) {
|
||||||
|
total := len(cond.filters)
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
if !cond.filters[i].Match(req) {
|
||||||
|
return ctx.Next(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp, err := ctx.Next(req)
|
||||||
|
return h.HandleResponse(resp, err, ctx)
|
||||||
|
})
|
||||||
|
}
|
13
handle.go
13
handle.go
@@ -2,26 +2,31 @@ package mps
|
|||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
|
type Handle interface {
|
||||||
|
RequestHandle
|
||||||
|
ResponseHandle
|
||||||
|
}
|
||||||
|
|
||||||
type RequestHandle interface {
|
type RequestHandle interface {
|
||||||
Handle(req *http.Request, ctx *Context) (*http.Request, *http.Response)
|
HandleRequest(req *http.Request, ctx *Context) (*http.Request, *http.Response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A wrapper that would convert a function to a RequestHandle interface type
|
// A wrapper that would convert a function to a RequestHandle interface type
|
||||||
type RequestHandleFunc func(req *http.Request, ctx *Context) (*http.Request, *http.Response)
|
type RequestHandleFunc func(req *http.Request, ctx *Context) (*http.Request, *http.Response)
|
||||||
|
|
||||||
// RequestHandle.Handle(req, ctx) <=> RequestHandleFunc(req, ctx)
|
// RequestHandle.Handle(req, ctx) <=> RequestHandleFunc(req, ctx)
|
||||||
func (f RequestHandleFunc) Handle(req *http.Request, ctx *Context) (*http.Request, *http.Response) {
|
func (f RequestHandleFunc) HandleRequest(req *http.Request, ctx *Context) (*http.Request, *http.Response) {
|
||||||
return f(req, ctx)
|
return f(req, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResponseHandle interface {
|
type ResponseHandle interface {
|
||||||
Handle(resp *http.Response, err error, ctx *Context) (*http.Response, error)
|
HandleResponse(resp *http.Response, err error, ctx *Context) (*http.Response, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A wrapper that would convert a function to a ResponseHandle interface type
|
// A wrapper that would convert a function to a ResponseHandle interface type
|
||||||
type ResponseHandleFunc func(resp *http.Response, err error, ctx *Context) (*http.Response, error)
|
type ResponseHandleFunc func(resp *http.Response, err error, ctx *Context) (*http.Response, error)
|
||||||
|
|
||||||
// ResponseHandle.Handle(resp, ctx) <=> ResponseHandleFunc(resp, ctx)
|
// ResponseHandle.Handle(resp, ctx) <=> ResponseHandleFunc(resp, ctx)
|
||||||
func (f ResponseHandleFunc) Handle(resp *http.Response, err error, ctx *Context) (*http.Response, error) {
|
func (f ResponseHandleFunc) HandleResponse(resp *http.Response, err error, ctx *Context) (*http.Response, error) {
|
||||||
return f(resp, err, ctx)
|
return f(resp, err, ctx)
|
||||||
}
|
}
|
||||||
|
@@ -9,13 +9,13 @@ import (
|
|||||||
|
|
||||||
// The basic proxy type. Implements http.Handler.
|
// The basic proxy type. Implements http.Handler.
|
||||||
type HttpProxy struct {
|
type HttpProxy struct {
|
||||||
// HTTPS requests use the TunnelHandler proxy by default
|
// Handles Connect requests use the TunnelHandler by default
|
||||||
HttpsHandler http.Handler
|
HandleConnect http.Handler
|
||||||
|
|
||||||
// HTTP requests use the ForwardHandler proxy by default
|
// HTTP requests use the ForwardHandler by default
|
||||||
HttpHandler http.Handler
|
HttpHandler http.Handler
|
||||||
|
|
||||||
// HTTP requests use the ReverseHandler proxy by default
|
// HTTP requests use the ReverseHandler by default
|
||||||
ReverseHandler http.Handler
|
ReverseHandler http.Handler
|
||||||
|
|
||||||
// Client request Context
|
// Client request Context
|
||||||
@@ -27,10 +27,10 @@ func NewHttpProxy() *HttpProxy {
|
|||||||
ctx := NewContext()
|
ctx := NewContext()
|
||||||
return &HttpProxy{
|
return &HttpProxy{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
// default HTTP proxy
|
// default handles Connect method
|
||||||
|
HandleConnect: &TunnelHandler{Ctx: ctx},
|
||||||
|
// default handles HTTP request
|
||||||
HttpHandler: &ForwardHandler{Ctx: ctx},
|
HttpHandler: &ForwardHandler{Ctx: ctx},
|
||||||
// default HTTPS proxy
|
|
||||||
HttpsHandler: &TunnelHandler{Ctx: ctx},
|
|
||||||
// default Reverse proxy
|
// default Reverse proxy
|
||||||
ReverseHandler: &ReverseHandler{Ctx: ctx},
|
ReverseHandler: &ReverseHandler{Ctx: ctx},
|
||||||
}
|
}
|
||||||
@@ -39,10 +39,21 @@ func NewHttpProxy() *HttpProxy {
|
|||||||
// Standard net/http function.
|
// Standard net/http function.
|
||||||
func (proxy *HttpProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (proxy *HttpProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
if req.Method == http.MethodConnect {
|
if req.Method == http.MethodConnect {
|
||||||
proxy.HttpsHandler.ServeHTTP(rw, req)
|
proxy.HandleConnect.ServeHTTP(rw, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reverse proxy http request for example:
|
||||||
|
// GET / HTTP/1.1
|
||||||
|
// Host: www.example.com
|
||||||
|
// Connection: keep-alive
|
||||||
|
//
|
||||||
|
// forward proxy http request for example :
|
||||||
|
// GET http://www.example.com/ HTTP/1.1
|
||||||
|
// Host: www.example.com
|
||||||
|
// Proxy-Connection: keep-alive
|
||||||
|
//
|
||||||
|
// Determines whether the path is absolute
|
||||||
if !req.URL.IsAbs() {
|
if !req.URL.IsAbs() {
|
||||||
proxy.ReverseHandler.ServeHTTP(rw, req)
|
proxy.ReverseHandler.ServeHTTP(rw, req)
|
||||||
} else {
|
} else {
|
||||||
|
@@ -51,8 +51,32 @@ func NewMitmHandler() *MitmHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a MitmHandler, use default cert.
|
||||||
|
func NewMitmHandlerWithContext(ctx *Context) *MitmHandler {
|
||||||
|
return &MitmHandler{
|
||||||
|
Ctx: ctx,
|
||||||
|
BufferPool: pool.DefaultBuffer,
|
||||||
|
Certificate: cert.DefaultCertificate,
|
||||||
|
CertContainer: cert.NewMemProvider(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a MitmHandler with cert pem block
|
||||||
|
func NewMitmHandlerWithCert(certPEMBlock, keyPEMBlock []byte) (*MitmHandler, error) {
|
||||||
|
certificate, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &MitmHandler{
|
||||||
|
Ctx: NewContext(),
|
||||||
|
BufferPool: pool.DefaultBuffer,
|
||||||
|
Certificate: certificate,
|
||||||
|
CertContainer: cert.NewMemProvider(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Create a MitmHandler with cert file
|
// Create a MitmHandler with cert file
|
||||||
func NewMitmHandlerWithCert(certFile, keyFile string) (*MitmHandler, error) {
|
func NewMitmHandlerWithCertFile(certFile, keyFile string) (*MitmHandler, error) {
|
||||||
certificate, err := tls.LoadX509KeyPair(certFile, keyFile)
|
certificate, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -67,7 +91,7 @@ func NewMitmHandlerWithCert(certFile, keyFile string) (*MitmHandler, error) {
|
|||||||
|
|
||||||
// Standard net/http function. You can use it alone
|
// Standard net/http function. You can use it alone
|
||||||
func (mitm *MitmHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
func (mitm *MitmHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||||
// Execution middleware
|
// execution middleware
|
||||||
ctx := mitm.Ctx.WithRequest(r)
|
ctx := mitm.Ctx.WithRequest(r)
|
||||||
resp, err := ctx.Next(r)
|
resp, err := ctx.Next(r)
|
||||||
if err != nil && err != MethodNotSupportErr {
|
if err != nil && err != MethodNotSupportErr {
|
||||||
|
@@ -1,33 +0,0 @@
|
|||||||
package mps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReqCondition is a request condition group
|
|
||||||
type ReqFilterGroup struct {
|
|
||||||
ctx *Context
|
|
||||||
filters []Filter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cond *ReqFilterGroup) DoFunc(fn func(req *http.Request, ctx *Context) (*http.Request, *http.Response)) {
|
|
||||||
cond.Do(RequestHandleFunc(fn))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cond *ReqFilterGroup) Do(h RequestHandle) {
|
|
||||||
cond.ctx.UseFunc(func(req *http.Request, ctx *Context) (*http.Response, error) {
|
|
||||||
total := len(cond.filters)
|
|
||||||
for i := 0; i < total; i++ {
|
|
||||||
if !cond.filters[i].Match(req) {
|
|
||||||
return ctx.Next(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
req, resp := h.Handle(req, ctx)
|
|
||||||
if resp != nil {
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctx.Next(req)
|
|
||||||
})
|
|
||||||
}
|
|
@@ -1,27 +0,0 @@
|
|||||||
package mps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RespFilterGroup struct {
|
|
||||||
ctx *Context
|
|
||||||
filters []Filter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cond *RespFilterGroup) DoFunc(fn func(resp *http.Response, err error, ctx *Context) (*http.Response, error)) {
|
|
||||||
cond.Do(ResponseHandleFunc(fn))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cond *RespFilterGroup) Do(h ResponseHandle) {
|
|
||||||
cond.ctx.UseFunc(func(req *http.Request, ctx *Context) (*http.Response, error) {
|
|
||||||
total := len(cond.filters)
|
|
||||||
for i := 0; i < total; i++ {
|
|
||||||
if !cond.filters[i].Match(req) {
|
|
||||||
return ctx.Next(req)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resp, err := ctx.Next(req)
|
|
||||||
return h.Handle(resp, err, ctx)
|
|
||||||
})
|
|
||||||
}
|
|
@@ -43,7 +43,7 @@ func NewTunnelHandlerWithContext(ctx *Context) *TunnelHandler {
|
|||||||
|
|
||||||
// Standard net/http function. You can use it alone
|
// Standard net/http function. You can use it alone
|
||||||
func (tunnel *TunnelHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (tunnel *TunnelHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
// Execution middleware
|
// execution middleware
|
||||||
ctx := tunnel.Ctx.WithRequest(req)
|
ctx := tunnel.Ctx.WithRequest(req)
|
||||||
resp, err := ctx.Next(req)
|
resp, err := ctx.Next(req)
|
||||||
if err != nil && err != MethodNotSupportErr {
|
if err != nil && err != MethodNotSupportErr {
|
||||||
|
Reference in New Issue
Block a user