telebit/internal/mgmt/devices.go

184 lines
5.4 KiB
Go
Raw Permalink Normal View History

2020-11-13 09:43:17 +00:00
package mgmt
2020-05-30 23:14:40 +00:00
import (
2020-05-31 12:19:41 +00:00
"crypto/rand"
"encoding/base64"
2020-05-30 23:14:40 +00:00
"encoding/json"
2020-05-31 12:19:41 +00:00
"fmt"
2020-05-30 23:14:40 +00:00
"log"
"net/http"
2020-06-01 08:48:05 +00:00
"strings"
2020-05-30 23:14:40 +00:00
"time"
"git.rootprojects.org/root/telebit/internal/authutil"
2020-11-13 09:43:17 +00:00
"git.rootprojects.org/root/telebit/internal/mgmt/authstore"
2020-05-30 23:14:40 +00:00
"github.com/go-chi/chi"
)
func handleDeviceRoutes(r chi.Router) {
r.Route("/devices", func(r chi.Router) {
2020-05-31 12:19:41 +00:00
// only the admin can get past this point
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
claims, ok := ctx.Value(authutil.MWKey("claims")).(*authutil.Claims)
2020-05-31 12:19:41 +00:00
if !ok || "*" != claims.Slug {
2020-08-14 09:24:32 +00:00
msg := `{"error":"missing or invalid authorization token", "code":"E_TOKEN"}`
2020-05-31 12:19:41 +00:00
http.Error(w, msg+"\n", http.StatusUnprocessableEntity)
return
}
2020-05-30 23:14:40 +00:00
2020-05-31 12:19:41 +00:00
next.ServeHTTP(w, r.WithContext(ctx))
})
})
2020-05-30 23:14:40 +00:00
2020-05-31 12:19:41 +00:00
r.Post("/", func(w http.ResponseWriter, r *http.Request) {
2020-05-30 23:14:40 +00:00
auth := &authstore.Authorization{}
2020-05-31 12:19:41 +00:00
// Slug is mandatory, ID and MachinePPID must NOT be set
2020-05-30 23:14:40 +00:00
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&auth)
epoch := time.Time{}
2020-05-31 12:19:41 +00:00
if nil != err || "" != auth.ID || "" != auth.MachinePPID || "" == auth.Slug ||
2020-05-30 23:14:40 +00:00
epoch != auth.CreatedAt || epoch != auth.UpdatedAt || epoch != auth.DeletedAt {
result, _ := json.Marshal(&authstore.Authorization{})
msg, _ := json.Marshal(&struct {
Error string `json:"error"`
}{
Error: "expected JSON in the format " + string(result),
})
http.Error(w, string(msg), http.StatusUnprocessableEntity)
return
}
2020-05-31 12:19:41 +00:00
if "" == auth.SharedKey {
rnd := make([]byte, 16)
if _, err := rand.Read(rnd); nil != err {
panic(err)
}
auth.SharedKey = base64.RawURLEncoding.EncodeToString(rnd)
}
if len(auth.SharedKey) < 20 {
2020-08-14 09:24:32 +00:00
msg := `{"error":"shared_key must be >= 16 bytes", "code":"E_BAD_REQUEST"}`
2020-05-31 12:19:41 +00:00
http.Error(w, string(msg), http.StatusUnprocessableEntity)
return
}
pub := authstore.ToPublicKeyString(auth.SharedKey)
if "" == auth.PublicKey {
auth.PublicKey = pub
}
if len(auth.PublicKey) > 24 {
auth.PublicKey = auth.PublicKey[:24]
}
if pub != auth.PublicKey {
2020-08-14 09:24:32 +00:00
msg := `{"error":"public_key must be the first 24 bytes of the base64-encoded hash of the shared_key", "code":"E_BAD_REQUEST"}`
2020-05-31 12:19:41 +00:00
http.Error(w, msg+"\n", http.StatusUnprocessableEntity)
return
}
2020-05-30 23:14:40 +00:00
err = store.Add(auth)
if nil != err {
2020-08-14 09:24:32 +00:00
msg := `{"error":"not really sure what happened, but it didn't go well (check the logs)", "code":"E_BAD_SERVER"}`
2020-05-31 12:19:41 +00:00
if authstore.ErrExists == err {
2020-08-14 09:24:32 +00:00
msg = fmt.Sprintf(`{ "error": "%s", "code":"E_EXIST"}`, err.Error())
2020-05-31 12:19:41 +00:00
}
log.Printf("/api/devices/\n")
2020-05-30 23:14:40 +00:00
log.Println(err)
http.Error(w, msg, http.StatusInternalServerError)
return
}
result, _ := json.Marshal(auth)
2020-05-31 12:19:41 +00:00
w.Write([]byte(string(result) + "\n"))
2020-05-30 23:14:40 +00:00
})
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
2020-06-01 08:48:05 +00:00
var things []authstore.Authorization
var err error
if "true" == strings.Join(r.URL.Query()["inactive"], " ") {
things, err = store.Inactive()
} else {
things, err = store.Active()
}
if nil != err {
2020-08-14 09:24:32 +00:00
msg := `{"error":"not really sure what happened, but it didn't go well (check the logs)", "code":"E_SERVER"}`
log.Printf("/api/devices/\n")
log.Println(err)
http.Error(w, msg, http.StatusInternalServerError)
return
}
2020-06-01 08:48:05 +00:00
for i := range things {
auth := things[i]
// Redact private data
if "" != auth.MachinePPID {
auth.MachinePPID = "[redacted]"
}
if "" != auth.SharedKey {
auth.SharedKey = "[redacted]"
}
things[i] = auth
}
encoder := json.NewEncoder(w)
encoder.SetEscapeHTML(true)
_ = encoder.Encode(things)
})
2020-05-30 23:14:40 +00:00
r.Get("/{slug}", func(w http.ResponseWriter, r *http.Request) {
slug := chi.URLParam(r, "slug")
// TODO store should be concurrency-safe
auth, err := store.Get(slug)
if nil != err {
2020-08-14 09:24:32 +00:00
var msg string
if err == authstore.ErrNotFound {
msg = `{"error":"not really sure what happened, but it didn't go well (check the logs)", "code":"E_NOT_FOUND"}`
} else {
msg = `{"error":"not really sure what happened, but it didn't go well (check the logs)", "code":"E_SERVER"}`
}
2020-05-30 23:14:40 +00:00
log.Printf("/api/devices/%s\n", slug)
log.Println(err)
http.Error(w, msg, http.StatusInternalServerError)
return
}
// Redact private data
if "" != auth.MachinePPID {
auth.MachinePPID = "[redacted]"
}
if "" != auth.SharedKey {
auth.SharedKey = "[redacted]"
}
result, _ := json.Marshal(auth)
2020-05-31 12:19:41 +00:00
w.Write([]byte(string(result) + "\n"))
2020-05-30 23:14:40 +00:00
})
r.Delete("/{slug}", func(w http.ResponseWriter, r *http.Request) {
slug := chi.URLParam(r, "slug")
auth, err := store.Get(slug)
if nil == auth {
2020-08-14 09:24:32 +00:00
var msg string
if err == authstore.ErrNotFound {
msg = `{"error":"not really sure what happened, but it didn't go well (check the logs)", "code":"E_NOT_FOUND"}`
} else {
msg = `{"error":"not really sure what happened, but it didn't go well (check the logs)", "code":"E_SERVER"}`
}
2020-05-30 23:14:40 +00:00
log.Printf("/api/devices/%s\n", slug)
log.Println(err)
http.Error(w, msg, http.StatusInternalServerError)
return
}
2020-06-22 06:34:42 +00:00
if err := store.Delete(auth); nil != err {
2020-08-14 09:24:32 +00:00
msg := `{"error":"not really sure what happened, but it didn't go well (check the logs)", "code":"E_SERVER"}`
2020-06-22 06:34:42 +00:00
log.Printf("/api/devices/%s\n", slug)
log.Println(err)
http.Error(w, msg, http.StatusInternalServerError)
}
2020-05-31 12:19:41 +00:00
w.Write([]byte(`{"success":true}` + "\n"))
2020-05-30 23:14:40 +00:00
})
})
}