mirror of
https://github.com/plantroon/acme.sh.git
synced 2024-12-27 07:21:53 +00:00
commit
44c1572b8f
@ -360,6 +360,8 @@ You don't have to do anything manually!
|
||||
1. MyDevil.net (https://www.mydevil.net/)
|
||||
1. Core-Networks.de (https://core-networks.de)
|
||||
1. NederHost API (https://www.nederhost.nl/)
|
||||
1. Zone.ee (zone.eu) API (https://api.zone.eu/v2)
|
||||
1. UltraDNS API (https://portal.ultradns.com)
|
||||
|
||||
And:
|
||||
|
||||
|
28
acme.sh
28
acme.sh
@ -3751,7 +3751,7 @@ issue() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
Le_OrderFinalize="$(echo "$response" | tr -d '\r\n' | _egrep_o '"finalize" *: *"[^"]*"' | cut -d '"' -f 4)"
|
||||
Le_OrderFinalize="$(echo "$response" | _egrep_o '"finalize" *: *"[^"]*"' | cut -d '"' -f 4)"
|
||||
_debug Le_OrderFinalize "$Le_OrderFinalize"
|
||||
if [ -z "$Le_OrderFinalize" ]; then
|
||||
_err "Create new order error. Le_OrderFinalize not found. $response"
|
||||
@ -3763,7 +3763,7 @@ issue() {
|
||||
#for dns manual mode
|
||||
_savedomainconf "Le_OrderFinalize" "$Le_OrderFinalize"
|
||||
|
||||
_authorizations_seg="$(echo "$response" | tr -d '\r\n' | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
|
||||
_authorizations_seg="$(echo "$response" | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
|
||||
_debug2 _authorizations_seg "$_authorizations_seg"
|
||||
if [ -z "$_authorizations_seg" ]; then
|
||||
_err "_authorizations_seg not found."
|
||||
@ -3849,7 +3849,7 @@ $_authorizations_map"
|
||||
thumbprint="$(__calc_account_thumbprint)"
|
||||
fi
|
||||
|
||||
entry="$(printf "%s\n" "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
|
||||
entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
|
||||
_debug entry "$entry"
|
||||
if [ -z "$entry" ]; then
|
||||
_err "Error, can not get domain token entry $d"
|
||||
@ -3861,7 +3861,7 @@ $_authorizations_map"
|
||||
_on_issue_err "$_post_hook"
|
||||
return 1
|
||||
fi
|
||||
token="$(printf "%s\n" "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
|
||||
token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
|
||||
_debug token "$token"
|
||||
|
||||
if [ -z "$token" ]; then
|
||||
@ -3871,9 +3871,9 @@ $_authorizations_map"
|
||||
return 1
|
||||
fi
|
||||
if [ "$ACME_VERSION" = "2" ]; then
|
||||
uri="$(printf "%s\n" "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)"
|
||||
uri="$(echo "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)"
|
||||
else
|
||||
uri="$(printf "%s\n" "$entry" | _egrep_o '"uri":"[^"]*' | cut -d '"' -f 4)"
|
||||
uri="$(echo "$entry" | _egrep_o '"uri":"[^"]*' | cut -d '"' -f 4)"
|
||||
fi
|
||||
_debug uri "$uri"
|
||||
|
||||
@ -4194,7 +4194,7 @@ $_authorizations_map"
|
||||
fi
|
||||
|
||||
if [ "$status" = "invalid" ]; then
|
||||
error="$(echo "$response" | tr -d "\r\n" | _egrep_o '"error":\{[^\}]*')"
|
||||
error="$(echo "$response" | _egrep_o '"error":\{[^\}]*')"
|
||||
_debug2 error "$error"
|
||||
errordetail="$(echo "$error" | _egrep_o '"detail": *"[^"]*' | cut -d '"' -f 4)"
|
||||
_debug2 errordetail "$errordetail"
|
||||
@ -4260,7 +4260,7 @@ $_authorizations_map"
|
||||
while [ "$_link_cert_retry" -lt "$_MAX_CERT_RETRY" ]; do
|
||||
if _contains "$response" "\"status\":\"valid\""; then
|
||||
_debug "Order status is valid."
|
||||
Le_LinkCert="$(echo "$response" | tr -d '\r\n' | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
|
||||
Le_LinkCert="$(echo "$response" | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
|
||||
_debug Le_LinkCert "$Le_LinkCert"
|
||||
if [ -z "$Le_LinkCert" ]; then
|
||||
_err "Sign error, can not find Le_LinkCert"
|
||||
@ -5195,7 +5195,7 @@ _deactivate() {
|
||||
_err "Can not get domain new order."
|
||||
return 1
|
||||
fi
|
||||
_authorizations_seg="$(echo "$response" | tr -d '\r\n' | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
|
||||
_authorizations_seg="$(echo "$response" | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
|
||||
_debug2 _authorizations_seg "$_authorizations_seg"
|
||||
if [ -z "$_authorizations_seg" ]; then
|
||||
_err "_authorizations_seg not found."
|
||||
@ -5241,16 +5241,16 @@ _deactivate() {
|
||||
fi
|
||||
_debug "Trigger validation."
|
||||
vtype="$VTYPE_DNS"
|
||||
entry="$(printf "%s\n" "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
|
||||
entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
|
||||
_debug entry "$entry"
|
||||
if [ -z "$entry" ]; then
|
||||
_err "Error, can not get domain token $d"
|
||||
return 1
|
||||
fi
|
||||
token="$(printf "%s\n" "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
|
||||
token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
|
||||
_debug token "$token"
|
||||
|
||||
uri="$(printf "%s\n" "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*" | cut -d : -f 2,3 | tr -d '"')"
|
||||
uri="$(echo "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*" | cut -d : -f 2,3 | tr -d '"')"
|
||||
_debug uri "$uri"
|
||||
|
||||
keyauthorization="$token.$thumbprint"
|
||||
@ -5272,11 +5272,11 @@ _deactivate() {
|
||||
break
|
||||
fi
|
||||
|
||||
_vtype="$(printf "%s\n" "$entry" | _egrep_o '"type": *"[^"]*"' | cut -d : -f 2 | tr -d '"')"
|
||||
_vtype="$(echo "$entry" | _egrep_o '"type": *"[^"]*"' | cut -d : -f 2 | tr -d '"')"
|
||||
_debug _vtype "$_vtype"
|
||||
_info "Found $_vtype"
|
||||
|
||||
uri="$(printf "%s\n" "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*" | cut -d : -f 2,3 | tr -d '"')"
|
||||
uri="$(echo "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*" | cut -d : -f 2,3 | tr -d '"')"
|
||||
_debug uri "$uri"
|
||||
|
||||
if [ "$_d_type" ] && [ "$_d_type" != "$_vtype" ]; then
|
||||
|
@ -1310,6 +1310,46 @@ To issue a certificate run:
|
||||
acme.sh --issue --dns dns_nederhost -d example.com -d *.example.com
|
||||
```
|
||||
|
||||
## 69. Use Zone.ee DNS API
|
||||
|
||||
First, you'll need to retrive your API key. Estonian insructions https://help.zone.eu/kb/zoneid-api-v2/
|
||||
|
||||
```
|
||||
export ZONE_Username=yourusername
|
||||
export ZONE_Key=keygoeshere
|
||||
```
|
||||
|
||||
To issue a cert run:
|
||||
|
||||
```
|
||||
acme.sh --issue -d example.com -d www.example.com --dns dns_zone
|
||||
```
|
||||
|
||||
`ZONE_Username` and `ZONE_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
|
||||
|
||||
## 70. Use UltraDNS API
|
||||
|
||||
UltraDNS is a paid for service that provides DNS, as well as Web and Mail forwarding (as well as reporting, auditing, and advanced tools).
|
||||
|
||||
More information can be found here: https://www.security.neustar/lp/ultra20/index.html
|
||||
|
||||
The REST API documentation for this service is found here: https://portal.ultradns.com/static/docs/REST-API_User_Guide.pdf
|
||||
|
||||
Set your UltraDNS User name, and password; these would be the same you would use here:
|
||||
|
||||
https://portal.ultradns.com/ - or if you create an API only user, that username and password would be better utilized.
|
||||
|
||||
```
|
||||
export ULTRA_USR="abcd"
|
||||
export ULTRA_PWD="efgh"
|
||||
|
||||
To issue a cert run:
|
||||
|
||||
acme.sh --issue --dns dns_ultra -d example.com -d www.example.com
|
||||
```
|
||||
|
||||
`ULTRA_USR` and `ULTRA_PWD` will be saved in `~/.acme.sh/account.conf` and will be resued when needed.
|
||||
|
||||
# Use custom API
|
||||
|
||||
If your API is not supported yet, you can write your own DNS API.
|
||||
|
164
dnsapi/dns_ultra.sh
Normal file
164
dnsapi/dns_ultra.sh
Normal file
@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# ULTRA_USR="your_user_goes_here"
|
||||
#
|
||||
# ULTRA_PWD="some_password_goes_here"
|
||||
|
||||
ULTRA_API="https://restapi.ultradns.com/v2/"
|
||||
|
||||
#Usage: add _acme-challenge.www.domain.com "some_long_string_of_characters_go_here_from_lets_encrypt"
|
||||
dns_ultra_add() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
export txtvalue
|
||||
ULTRA_USR="${ULTRA_USR:-$(_readaccountconf_mutable ULTRA_USR)}"
|
||||
ULTRA_PWD="${ULTRA_PWD:-$(_readaccountconf_mutable ULTRA_PWD)}"
|
||||
if [ -z "$ULTRA_USR" ] || [ -z "$ULTRA_PWD" ]; then
|
||||
ULTRA_USR=""
|
||||
ULTRA_PWD=""
|
||||
_err "You didn't specify an UltraDNS username and password yet"
|
||||
return 1
|
||||
fi
|
||||
# save the username and password to the account conf file.
|
||||
_saveaccountconf_mutable ULTRA_USR "$ULTRA_USR"
|
||||
_saveaccountconf_mutable ULTRA_PWD "$ULTRA_PWD"
|
||||
_debug "First detect the root zone"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "invalid domain"
|
||||
return 1
|
||||
fi
|
||||
_debug _domain_id "${_domain_id}"
|
||||
_debug _sub_domain "${_sub_domain}"
|
||||
_debug _domain "${_domain}"
|
||||
_debug "Getting txt records"
|
||||
_ultra_rest GET "zones/${_domain_id}/rrsets/TXT?q=value:${fulldomain}"
|
||||
if printf "%s" "$response" | grep \"totalCount\" >/dev/null; then
|
||||
_err "Error, it would appear that this record already exists. Please review existing TXT records for this domain."
|
||||
return 1
|
||||
fi
|
||||
|
||||
_info "Adding record"
|
||||
if _ultra_rest POST "zones/$_domain_id/rrsets/TXT/${_sub_domain}" '{"ttl":300,"rdata":["'"${txtvalue}"'"]}'; then
|
||||
if _contains "$response" "Successful"; then
|
||||
_info "Added, OK"
|
||||
return 0
|
||||
elif _contains "$response" "Resource Record of type 16 with these attributes already exists"; then
|
||||
_info "Already exists, OK"
|
||||
return 0
|
||||
else
|
||||
_err "Add txt record error."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
_err "Add txt record error."
|
||||
|
||||
}
|
||||
|
||||
dns_ultra_rm() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
export txtvalue
|
||||
ULTRA_USR="${ULTRA_USR:-$(_readaccountconf_mutable ULTRA_USR)}"
|
||||
ULTRA_PWD="${ULTRA_PWD:-$(_readaccountconf_mutable ULTRA_PWD)}"
|
||||
if [ -z "$ULTRA_USR" ] || [ -z "$ULTRA_PWD" ]; then
|
||||
ULTRA_USR=""
|
||||
ULTRA_PWD=""
|
||||
_err "You didn't specify an UltraDNS username and password yet"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "First detect the root zone"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "invalid domain"
|
||||
return 1
|
||||
fi
|
||||
_debug _domain_id "${_domain_id}"
|
||||
_debug _sub_domain "${_sub_domain}"
|
||||
_debug _domain "${domain}"
|
||||
|
||||
_debug "Getting TXT records"
|
||||
_ultra_rest GET "zones/${_domain_id}/rrsets?q=kind:RECORDS+owner:${_sub_domain}"
|
||||
|
||||
if ! printf "%s" "$response" | grep \"resultInfo\" >/dev/null; then
|
||||
_err "There was an error in obtaining the resource records for ${_domain_id}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
count=$(echo "$response" | _egrep_o "\"returnedCount\":[^,]*" | cut -d: -f2 | cut -d'}' -f1)
|
||||
_debug count "${count}"
|
||||
if [ "${count}" = "" ]; then
|
||||
_info "Text record is not present, will not delete anything."
|
||||
else
|
||||
if ! _ultra_rest DELETE "zones/$_domain_id/rrsets/TXT/${_sub_domain}" '{"ttl":300,"rdata":["'"${txtvalue}"'"]}'; then
|
||||
_err "Deleting the record did not succeed, please verify/check."
|
||||
return 1
|
||||
fi
|
||||
_contains "$response" ""
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
#_acme-challenge.www.domain.com
|
||||
#returns
|
||||
# _sub_domain=_acme-challenge.www
|
||||
# _domain=domain.com
|
||||
# _domain_id=sdjkglgdfewsdfg
|
||||
_get_root() {
|
||||
domain=$1
|
||||
i=2
|
||||
p=1
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
|
||||
_debug h "$h"
|
||||
_debug response "$response"
|
||||
if [ -z "$h" ]; then
|
||||
#not valid
|
||||
return 1
|
||||
fi
|
||||
if ! _ultra_rest GET "zones"; then
|
||||
return 1
|
||||
fi
|
||||
if _contains "${response}" "${h}." >/dev/null; then
|
||||
_domain_id=$(echo "$response" | _egrep_o "${h}")
|
||||
if [ "$_domain_id" ]; then
|
||||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
|
||||
_domain="${h}"
|
||||
_debug sub_domain "${_sub_domain}"
|
||||
_debug domain "${_domain}"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
p=$i
|
||||
i=$(_math "$i" + 1)
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
_ultra_rest() {
|
||||
m=$1
|
||||
ep="$2"
|
||||
data="$3"
|
||||
_debug "$ep"
|
||||
_debug TOKEN "${AUTH_TOKEN}"
|
||||
|
||||
_ultra_login
|
||||
export _H1="Content-Type: application/json"
|
||||
export _H2="Authorization: Bearer ${AUTH_TOKEN}"
|
||||
|
||||
if [ "$m" != "GET" ]; then
|
||||
_debug data "${data}"
|
||||
response="$(_post "${data}" "${ULTRA_API}"/"${ep}" "" "${m}")"
|
||||
else
|
||||
response="$(_get "$ULTRA_API/$ep")"
|
||||
fi
|
||||
}
|
||||
|
||||
_ultra_login() {
|
||||
export _H1=""
|
||||
export _H2=""
|
||||
AUTH_TOKEN=$(_post "grant_type=password&username=${ULTRA_USR}&password=${ULTRA_PWD}" "${ULTRA_API}authorization/token" | cut -d, -f3 | cut -d\" -f4)
|
||||
export AUTH_TOKEN
|
||||
}
|
149
dnsapi/dns_zone.sh
Executable file
149
dnsapi/dns_zone.sh
Executable file
@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Zone.ee dns API
|
||||
# https://help.zone.eu/kb/zoneid-api-v2/
|
||||
# required ZONE_Username and ZONE_Key
|
||||
|
||||
ZONE_Api="https://api.zone.eu/v2"
|
||||
######## Public functions #####################
|
||||
|
||||
#Usage: dns_zone_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
|
||||
dns_zone_add() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
_info "Using zone.ee dns api"
|
||||
_debug fulldomain "$fulldomain"
|
||||
_debug txtvalue "$txtvalue"
|
||||
ZONE_Username="${ZONE_Username:-$(_readaccountconf_mutable ZONE_Username)}"
|
||||
ZONE_Key="${ZONE_Key:-$(_readaccountconf_mutable ZONE_Key)}"
|
||||
if [ -z "$ZONE_Username" ] || [ -z "$ZONE_Key" ]; then
|
||||
ZONE_Username=""
|
||||
ZONE_Key=""
|
||||
_err "Zone api key and username must be present."
|
||||
return 1
|
||||
fi
|
||||
_saveaccountconf_mutable ZONE_Username "$ZONE_Username"
|
||||
_saveaccountconf_mutable ZONE_Key "$ZONE_Key"
|
||||
_debug "First detect the root zone"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "invalid domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "Adding txt record"
|
||||
|
||||
if _zone_rest POST "dns/${_domain}/txt" "{\"name\": \"$fulldomain\", \"destination\": \"$txtvalue\"}"; then
|
||||
if printf -- "%s" "$response" | grep "$fulldomain" >/dev/null; then
|
||||
_info "Added, OK"
|
||||
return 0
|
||||
else
|
||||
_err "Adding txt record error."
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
_err "Adding txt record error."
|
||||
fi
|
||||
}
|
||||
|
||||
#Usage: fulldomain txtvalue
|
||||
#Remove the txt record after validation.
|
||||
dns_zone_rm() {
|
||||
fulldomain=$1
|
||||
txtvalue=$2
|
||||
_info "Using zone.ee dns api"
|
||||
_debug fulldomain "$fulldomain"
|
||||
_debug txtvalue "$txtvalue"
|
||||
ZONE_Username="${ZONE_Username:-$(_readaccountconf_mutable ZONE_Username)}"
|
||||
ZONE_Key="${ZONE_Key:-$(_readaccountconf_mutable ZONE_Key)}"
|
||||
if [ -z "$ZONE_Username" ] || [ -z "$ZONE_Key" ]; then
|
||||
ZONE_Username=""
|
||||
ZONE_Key=""
|
||||
_err "Zone api key and username must be present."
|
||||
return 1
|
||||
fi
|
||||
_saveaccountconf_mutable ZONE_Username "$ZONE_Username"
|
||||
_saveaccountconf_mutable ZONE_Key "$ZONE_Key"
|
||||
_debug "First detect the root zone"
|
||||
if ! _get_root "$fulldomain"; then
|
||||
_err "invalid domain"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_debug "Getting txt records"
|
||||
_debug _domain "$_domain"
|
||||
|
||||
_zone_rest GET "dns/${_domain}/txt"
|
||||
|
||||
if printf "%s" "$response" | grep \"error\" >/dev/null; then
|
||||
_err "Error"
|
||||
return 1
|
||||
fi
|
||||
|
||||
count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$fulldomain\"" | wc -l)
|
||||
_debug count "$count"
|
||||
if [ "$count" = "0" ]; then
|
||||
_info "Nothing to remove."
|
||||
else
|
||||
record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\",\"resource_url\":\"[^\"]*\",\"name\":\"$fulldomain\"," | cut -d : -f2 | cut -d , -f1 | tr -d \" | _head_n 1)
|
||||
if [ -z "$record_id" ]; then
|
||||
_err "No id found to remove."
|
||||
return 1
|
||||
fi
|
||||
if ! _zone_rest DELETE "dns/${_domain}/txt/$record_id"; then
|
||||
_err "Record deleting error."
|
||||
return 1
|
||||
fi
|
||||
_info "Record deleted"
|
||||
return 0
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
#################### Private functions below ##################################
|
||||
|
||||
_zone_rest() {
|
||||
m=$1
|
||||
ep="$2"
|
||||
data="$3"
|
||||
_debug "$ep"
|
||||
|
||||
realm="$(printf "%s" "$ZONE_Username:$ZONE_Key" | _base64)"
|
||||
|
||||
export _H1="Authorization: Basic $realm"
|
||||
export _H2="Content-Type: application/json"
|
||||
|
||||
if [ "$m" != "GET" ]; then
|
||||
_debug data "$data"
|
||||
response="$(_post "$data" "$ZONE_Api/$ep" "" "$m")"
|
||||
else
|
||||
response="$(_get "$ZONE_Api/$ep")"
|
||||
fi
|
||||
|
||||
if [ "$?" != "0" ]; then
|
||||
_err "error $ep"
|
||||
return 1
|
||||
fi
|
||||
_debug2 response "$response"
|
||||
return 0
|
||||
}
|
||||
|
||||
_get_root() {
|
||||
domain=$1
|
||||
i=2
|
||||
while true; do
|
||||
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
|
||||
_debug h "$h"
|
||||
if [ -z "$h" ]; then
|
||||
return 1
|
||||
fi
|
||||
if ! _zone_rest GET "dns/$h/a"; then
|
||||
return 1
|
||||
fi
|
||||
if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
|
||||
_domain=$h
|
||||
return 0
|
||||
fi
|
||||
i=$(_math "$i" + 1)
|
||||
done
|
||||
return 0
|
||||
}
|
Loading…
Reference in New Issue
Block a user