mirror of
https://github.com/joohoi/acme-dns.git
synced 2025-03-09 20:29:14 +07:00
Add API endpoint to delete a registered user (resolves joohoi/acme-dns#177)
This commit is contained in:
parent
19069f50ec
commit
7910db24da
20
README.md
20
README.md
@ -75,6 +75,26 @@ With the credentials, you can update the TXT response in the service to match th
|
||||
}
|
||||
```
|
||||
|
||||
### Unregister endpoint
|
||||
|
||||
This method deletes a registered user and the associated TXT records from the database. The subdomain has to be sent as JSON input. The user's credentials must be provided like with an update update request.
|
||||
|
||||
#### Example input
|
||||
```json
|
||||
{
|
||||
"subdomain": "0b3736b4-32dd-43c1-8b4f-db117767b3ca"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```Status: 200 OK```
|
||||
```json
|
||||
{
|
||||
"unregister": "c2d7e8fd-ff4c-41c6-8f16-1d76628cbfde"
|
||||
}
|
||||
```
|
||||
|
||||
### Update endpoint
|
||||
|
||||
The method allows you to update the TXT answer contents of your unique subdomain. Usually carried automatically by automated ACME client.
|
||||
|
33
api.go
33
api.go
@ -6,6 +6,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
@ -19,6 +20,12 @@ type RegResponse struct {
|
||||
Allowfrom []string `json:"allowfrom"`
|
||||
}
|
||||
|
||||
// UnregRequest is a struct providing the data to unregister
|
||||
type UnregRequest struct {
|
||||
Username uuid.UUID `json:"username"`
|
||||
Subdomain string `json:"subdomain"`
|
||||
}
|
||||
|
||||
func webRegisterPost(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
var regStatus int
|
||||
var reg []byte
|
||||
@ -71,6 +78,32 @@ func webRegisterPost(w http.ResponseWriter, r *http.Request, _ httprouter.Params
|
||||
w.Write(reg)
|
||||
}
|
||||
|
||||
func webUnregisterPost(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
var unregStatus int
|
||||
var err error
|
||||
var upd []byte
|
||||
|
||||
// Get user data
|
||||
unregData, ok := r.Context().Value(ACMETxtKey).(UnregRequest)
|
||||
if !ok {
|
||||
log.WithFields(log.Fields{"error": "context"}).Error("Context error")
|
||||
}
|
||||
|
||||
// Delete user
|
||||
err = DB.Unregister(unregData.Username)
|
||||
if err != nil {
|
||||
unregStatus = http.StatusInternalServerError
|
||||
upd = jsonError(fmt.Sprintf("%s (%v)", "delete_error", err))
|
||||
} else {
|
||||
log.WithFields(log.Fields{"user": unregData.Username.String()}).Debug("Deleted user")
|
||||
upd = []byte("{\"unregister\": \"" + unregData.Username.String() + "\"}")
|
||||
unregStatus = http.StatusOK
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(unregStatus)
|
||||
w.Write(upd)
|
||||
}
|
||||
|
||||
func webUpdatePost(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
var updStatus int
|
||||
var upd []byte
|
||||
|
41
api_test.go
41
api_test.go
@ -73,9 +73,11 @@ func setupRouter(debug bool, noauth bool) http.Handler {
|
||||
api.POST("/register", webRegisterPost)
|
||||
api.GET("/health", healthCheck)
|
||||
if noauth {
|
||||
api.POST("/unregister", noAuth(webUnregisterPost))
|
||||
api.POST("/update", noAuth(webUpdatePost))
|
||||
} else {
|
||||
api.POST("/update", Auth(webUpdatePost))
|
||||
api.POST("/unregister", AuthUnregister(webUnregisterPost))
|
||||
api.POST("/update", AuthUpdate(webUpdatePost))
|
||||
}
|
||||
return c.Handler(api)
|
||||
}
|
||||
@ -191,6 +193,43 @@ func TestApiRegisterWithMockDB(t *testing.T) {
|
||||
DB.SetBackend(oldDb)
|
||||
}
|
||||
|
||||
func TestApiUnregisterWithoutCredentials(t *testing.T) {
|
||||
router := setupRouter(false, false)
|
||||
server := httptest.NewServer(router)
|
||||
defer server.Close()
|
||||
e := getExpect(t, server)
|
||||
e.POST("/unregister").Expect().
|
||||
Status(http.StatusUnauthorized).
|
||||
JSON().Object().
|
||||
ContainsKey("error")
|
||||
}
|
||||
|
||||
func TestApiUnregisterInvalidSubdomain(t *testing.T) {
|
||||
unregisterJSON := map[string]interface{}{
|
||||
"subdomain": ""}
|
||||
|
||||
// Invalid username
|
||||
unregisterJSON["subdomain"] = "example.com"
|
||||
|
||||
router := setupRouter(false, false)
|
||||
server := httptest.NewServer(router)
|
||||
defer server.Close()
|
||||
e := getExpect(t, server)
|
||||
newUser, err := DB.Register(cidrslice{})
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Could not create new user, got error [%v]", err)
|
||||
}
|
||||
e.POST("/unregister").
|
||||
WithJSON(unregisterJSON).
|
||||
WithHeader("X-Api-User", newUser.Username.String()).
|
||||
WithHeader("X-Api-Key", newUser.Password).
|
||||
Expect().
|
||||
Status(http.StatusUnauthorized).
|
||||
JSON().Object().
|
||||
ContainsKey("error")
|
||||
}
|
||||
|
||||
func TestApiUpdateWithInvalidSubdomain(t *testing.T) {
|
||||
validTxtData := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
|
||||
|
38
auth.go
38
auth.go
@ -16,8 +16,8 @@ type key int
|
||||
// ACMETxtKey is a context key for ACMETxt struct
|
||||
const ACMETxtKey key = 0
|
||||
|
||||
// Auth middleware for update request
|
||||
func Auth(update httprouter.Handle) httprouter.Handle {
|
||||
// AuthUpdate middleware for update request
|
||||
func AuthUpdate(update httprouter.Handle) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
postData := ACMETxt{}
|
||||
userOK := false
|
||||
@ -55,6 +55,40 @@ func Auth(update httprouter.Handle) httprouter.Handle {
|
||||
}
|
||||
}
|
||||
|
||||
// AuthUnregister middleware for unregister request
|
||||
func AuthUnregister(unregister httprouter.Handle) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
postData := UnregRequest{}
|
||||
userOK := false
|
||||
user, err := getUserFromRequest(r)
|
||||
if err == nil {
|
||||
dec := json.NewDecoder(r.Body)
|
||||
err = dec.Decode(&postData)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{"error": "json_error", "string": err.Error()}).Error("Decode error")
|
||||
}
|
||||
if user.Subdomain == postData.Subdomain {
|
||||
userOK = true
|
||||
} else {
|
||||
log.WithFields(log.Fields{"error": "subdomain_mismatch", "name": postData.Subdomain, "expected": user.Subdomain}).Error("Subdomain mismatch")
|
||||
}
|
||||
} else {
|
||||
log.WithFields(log.Fields{"error": err.Error()}).Error("Error while trying to get user")
|
||||
}
|
||||
if userOK {
|
||||
// Set username info to the decoded UnregRequest object
|
||||
postData.Username = user.Username
|
||||
// Set the UnregRequest struct to context to pull in from unregister function
|
||||
ctx := context.WithValue(r.Context(), ACMETxtKey, postData)
|
||||
unregister(w, r.WithContext(ctx), p)
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write(jsonError("forbidden"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getUserFromRequest(r *http.Request) (ACMETxt, error) {
|
||||
uname := r.Header.Get("X-Api-User")
|
||||
passwd := r.Header.Get("X-Api-Key")
|
||||
|
73
db.go
73
db.go
@ -209,6 +209,58 @@ func (d *acmedb) Register(afrom cidrslice) (ACMETxt, error) {
|
||||
return a, err
|
||||
}
|
||||
|
||||
func (d *acmedb) Unregister(username uuid.UUID) error {
|
||||
var err error
|
||||
|
||||
// Check if user exists
|
||||
user, err := DB.GetByUsername(username)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{"error": err.Error()}).Error("User doesn't exist")
|
||||
return errors.New("User doesn't exist")
|
||||
}
|
||||
|
||||
tx, err := d.DB.Begin()
|
||||
|
||||
// Delete user's TXT records
|
||||
err = d.DeleteTXTForDomain(user.Subdomain)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{"error": err.Error()}).Error("Could not delete TXT records")
|
||||
return err
|
||||
}
|
||||
|
||||
// Rollback if errored, commit if not
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
}()
|
||||
|
||||
// Delete user record
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
unregSQL := `
|
||||
DELETE FROM records
|
||||
WHERE Username = $1`
|
||||
if Config.Database.Engine == "sqlite3" {
|
||||
unregSQL = getSQLiteStmt(unregSQL)
|
||||
}
|
||||
sm, err := tx.Prepare(unregSQL)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{"error": err.Error()}).Error("Database error in prepare")
|
||||
return errors.New("SQL error")
|
||||
}
|
||||
defer sm.Close()
|
||||
_, err = sm.Exec(username.String())
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{"error": err.Error()}).Error("Database error in execute")
|
||||
return errors.New("SQL error")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *acmedb) GetByUsername(u uuid.UUID) (ACMETxt, error) {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
@ -309,6 +361,27 @@ func (d *acmedb) Update(a ACMETxtPost) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *acmedb) DeleteTXTForDomain(domain string) error {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
domain = sanitizeString(domain)
|
||||
getSQL := `DELETE FROM txt WHERE Subdomain=$1`
|
||||
if Config.Database.Engine == "sqlite3" {
|
||||
getSQL = getSQLiteStmt(getSQL)
|
||||
}
|
||||
|
||||
sm, err := d.DB.Prepare(getSQL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sm.Close()
|
||||
_, err = sm.Exec(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getModelFromRow(r *sql.Rows) (ACMETxt, error) {
|
||||
txt := ACMETxt{}
|
||||
afrom := ""
|
||||
|
3
main.go
3
main.go
@ -125,8 +125,9 @@ func startHTTPAPI(errChan chan error, config DNSConfig, dnsservers []*DNSServer)
|
||||
}
|
||||
if !Config.API.DisableRegistration {
|
||||
api.POST("/register", webRegisterPost)
|
||||
api.POST("/unregister", AuthUnregister(webUnregisterPost))
|
||||
}
|
||||
api.POST("/update", Auth(webUpdatePost))
|
||||
api.POST("/update", AuthUpdate(webUpdatePost))
|
||||
api.GET("/health", healthCheck)
|
||||
|
||||
host := Config.API.IP + ":" + Config.API.Port
|
||||
|
Loading…
Reference in New Issue
Block a user