Version 3 starter

This commit is contained in:
Jamie Curnow
2021-06-14 19:29:35 +10:00
parent 60fc57431a
commit 6205434140
642 changed files with 25817 additions and 32319 deletions

View File

@@ -0,0 +1,54 @@
package handler
import (
"encoding/json"
"net/http"
c "npm/internal/api/context"
h "npm/internal/api/http"
"npm/internal/entity/auth"
"npm/internal/logger"
)
// SetAuth ...
// Route: POST /users/:userID/auth
func SetAuth() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
// TODO:
// delete old auth for user
// test endpoint
var newAuth auth.Model
err := json.Unmarshal(bodyBytes, &newAuth)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
userID, _, userIDErr := getUserIDFromRequest(r)
if userIDErr != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, userIDErr.Error(), nil)
return
}
newAuth.UserID = userID
if newAuth.Type == auth.TypePassword {
err := newAuth.SetPassword(newAuth.Secret)
if err != nil {
logger.Error("SetPasswordError", err)
}
}
if err = newAuth.Save(); err != nil {
logger.Error("AuthSaveError", err)
h.ResultErrorJSON(w, r, http.StatusInternalServerError, "Unable to save Authentication for User", nil)
return
}
newAuth.Secret = ""
h.ResultResponseJSON(w, r, http.StatusOK, newAuth)
}
}

View File

@@ -0,0 +1,126 @@
package handler
import (
"encoding/json"
"fmt"
"net/http"
c "npm/internal/api/context"
h "npm/internal/api/http"
"npm/internal/api/middleware"
"npm/internal/entity/certificateauthority"
)
// GetCertificateAuthorities will return a list of Certificate Authorities
// Route: GET /certificate-authorities
func GetCertificateAuthorities() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
pageInfo, err := getPageInfoFromRequest(r)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
certificates, err := certificateauthority.List(pageInfo, middleware.GetFiltersFromContext(r))
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, certificates)
}
}
}
// GetCertificateAuthority will return a single Certificate Authority
// Route: GET /certificate-authorities/{caID}
func GetCertificateAuthority() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var caID int
if caID, err = getURLParamInt(r, "caID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
cert, err := certificateauthority.GetByID(caID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, cert)
}
}
}
// CreateCertificateAuthority will create a Certificate Authority
// Route: POST /certificate-authorities
func CreateCertificateAuthority() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
var newCA certificateauthority.Model
err := json.Unmarshal(bodyBytes, &newCA)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
if err = newCA.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, fmt.Sprintf("Unable to save Certificate Authority: %s", err.Error()), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, newCA)
}
}
// UpdateCertificateAuthority ...
// Route: PUT /certificate-authorities/{caID}
func UpdateCertificateAuthority() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var caID int
if caID, err = getURLParamInt(r, "caID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
ca, err := certificateauthority.GetByID(caID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
err := json.Unmarshal(bodyBytes, &ca)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
if err = ca.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, ca)
}
}
}
// DeleteCertificateAuthority ...
// Route: DELETE /certificate-authorities/{caID}
func DeleteCertificateAuthority() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var caID int
if caID, err = getURLParamInt(r, "caID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
cert, err := certificateauthority.GetByID(caID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, cert.Delete())
}
}
}

View File

@@ -0,0 +1,145 @@
package handler
import (
"encoding/json"
"fmt"
"net/http"
c "npm/internal/api/context"
h "npm/internal/api/http"
"npm/internal/api/middleware"
"npm/internal/api/schema"
"npm/internal/entity/certificate"
)
// GetCertificates will return a list of Certificates
// Route: GET /certificates
func GetCertificates() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
pageInfo, err := getPageInfoFromRequest(r)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
certificates, err := certificate.List(pageInfo, middleware.GetFiltersFromContext(r))
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, certificates)
}
}
}
// GetCertificate will return a single Certificate
// Route: GET /certificates/{certificateID}
func GetCertificate() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var certificateID int
if certificateID, err = getURLParamInt(r, "certificateID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
cert, err := certificate.GetByID(certificateID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, cert)
}
}
}
// CreateCertificate will create a Certificate
// Route: POST /certificates
func CreateCertificate() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
var newCertificate certificate.Model
err := json.Unmarshal(bodyBytes, &newCertificate)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
// Get userID from token
userID, _ := r.Context().Value(c.UserIDCtxKey).(int)
newCertificate.UserID = userID
if err = newCertificate.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, fmt.Sprintf("Unable to save Certificate: %s", err.Error()), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, newCertificate)
}
}
// UpdateCertificate ...
// Route: PUT /certificates/{certificateID}
func UpdateCertificate() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var certificateID int
if certificateID, err = getURLParamInt(r, "certificateID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
certificateObject, err := certificate.GetByID(certificateID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
// This is a special endpoint, as it needs to verify the schema payload
// based on the certificate type, without being given a type in the payload.
// The middleware would normally handle this.
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
schemaErrors, jsonErr := middleware.CheckRequestSchema(r.Context(), schema.UpdateCertificate(certificateObject.Type), bodyBytes)
if jsonErr != nil {
h.ResultErrorJSON(w, r, http.StatusInternalServerError, fmt.Sprintf("Schema Fatal: %v", jsonErr), nil)
return
}
if len(schemaErrors) > 0 {
h.ResultSchemaErrorJSON(w, r, schemaErrors)
return
}
err := json.Unmarshal(bodyBytes, &certificateObject)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
if err = certificateObject.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, certificateObject)
}
}
}
// DeleteCertificate ...
// Route: DELETE /certificates/{certificateID}
func DeleteCertificate() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var certificateID int
if certificateID, err = getURLParamInt(r, "certificateID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
cert, err := certificate.GetByID(certificateID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, cert.Delete())
}
}
}

View File

@@ -0,0 +1,15 @@
package handler
import (
"net/http"
h "npm/internal/api/http"
"npm/internal/config"
)
// Config returns the entire configuration, for debug purposes
// Route: GET /config
func Config() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
h.ResultResponseJSON(w, r, http.StatusOK, config.Configuration)
}
}

View File

@@ -0,0 +1,129 @@
package handler
import (
"encoding/json"
"fmt"
"net/http"
c "npm/internal/api/context"
h "npm/internal/api/http"
"npm/internal/api/middleware"
"npm/internal/entity/dnsprovider"
)
// GetDNSProviders will return a list of DNS Providers
// Route: GET /dns-providers
func GetDNSProviders() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
pageInfo, err := getPageInfoFromRequest(r)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
items, err := dnsprovider.List(pageInfo, middleware.GetFiltersFromContext(r))
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, items)
}
}
}
// GetDNSProvider will return a single DNS Provider
// Route: GET /dns-providers/{providerID}
func GetDNSProvider() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var providerID int
if providerID, err = getURLParamInt(r, "providerID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
item, err := dnsprovider.GetByID(providerID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, item)
}
}
}
// CreateDNSProvider will create a DNS Provider
// Route: POST /dns-providers
func CreateDNSProvider() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
var newItem dnsprovider.Model
err := json.Unmarshal(bodyBytes, &newItem)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
// Get userID from token
userID, _ := r.Context().Value(c.UserIDCtxKey).(int)
newItem.UserID = userID
if err = newItem.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, fmt.Sprintf("Unable to save DNS Provider: %s", err.Error()), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, newItem)
}
}
// UpdateDNSProvider ...
// Route: PUT /dns-providers/{providerID}
func UpdateDNSProvider() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var providerID int
if providerID, err = getURLParamInt(r, "providerID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
item, err := dnsprovider.GetByID(providerID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
err := json.Unmarshal(bodyBytes, &item)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
if err = item.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, item)
}
}
}
// DeleteDNSProvider ...
// Route: DELETE /dns-providers/{providerID}
func DeleteDNSProvider() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var providerID int
if providerID, err = getURLParamInt(r, "providerID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
item, err := dnsprovider.GetByID(providerID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, item.Delete())
}
}
}

View File

@@ -0,0 +1,31 @@
package handler
import (
"net/http"
h "npm/internal/api/http"
"npm/internal/config"
)
type healthCheckResponse struct {
Version string `json:"version"`
Commit string `json:"commit"`
Healthy bool `json:"healthy"`
IsSetup bool `json:"setup"`
ErrorReporting bool `json:"error_reporting"`
}
// Health returns the health of the api
// Route: GET /health
func Health() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
health := healthCheckResponse{
Version: config.Version,
Commit: config.Commit,
Healthy: true,
IsSetup: config.IsSetup,
ErrorReporting: config.ErrorReporting,
}
h.ResultResponseJSON(w, r, http.StatusOK, health)
}
}

View File

@@ -0,0 +1,151 @@
package handler
import (
"fmt"
"net/http"
"strconv"
"strings"
"time"
"npm/internal/model"
"github.com/go-chi/chi"
)
const defaultLimit = 10
func getPageInfoFromRequest(r *http.Request) (model.PageInfo, error) {
var pageInfo model.PageInfo
var err error
pageInfo.FromDate, pageInfo.ToDate, err = getDateRanges(r)
if err != nil {
return pageInfo, err
}
pageInfo.Offset, pageInfo.Limit, err = getPagination(r)
if err != nil {
return pageInfo, err
}
pageInfo.Sort = getSortParameter(r)
return pageInfo, nil
}
func getDateRanges(r *http.Request) (time.Time, time.Time, error) {
queryValues := r.URL.Query()
from := queryValues.Get("from")
fromDate := time.Now().AddDate(0, -1, 0) // 1 month ago by default
to := queryValues.Get("to")
toDate := time.Now()
if from != "" {
var fromErr error
fromDate, fromErr = time.Parse(time.RFC3339, from)
if fromErr != nil {
return fromDate, toDate, fmt.Errorf("From date is not in correct format: %v", strings.ReplaceAll(time.RFC3339, "Z", "+"))
}
}
if to != "" {
var toErr error
toDate, toErr = time.Parse(time.RFC3339, to)
if toErr != nil {
return fromDate, toDate, fmt.Errorf("To date is not in correct format: %v", strings.ReplaceAll(time.RFC3339, "Z", "+"))
}
}
return fromDate, toDate, nil
}
func getSortParameter(r *http.Request) []model.Sort {
var sortFields []model.Sort
queryValues := r.URL.Query()
sortString := queryValues.Get("sort")
if sortString == "" {
return sortFields
}
// Split sort fields up in to slice
sorts := strings.Split(sortString, ",")
for _, sortItem := range sorts {
if strings.Contains(sortItem, ".") {
theseItems := strings.Split(sortItem, ".")
switch strings.ToLower(theseItems[1]) {
case "desc":
fallthrough
case "descending":
theseItems[1] = "DESC"
default:
theseItems[1] = "ASC"
}
sortFields = append(sortFields, model.Sort{
Field: theseItems[0],
Direction: theseItems[1],
})
} else {
sortFields = append(sortFields, model.Sort{
Field: sortItem,
Direction: "ASC",
})
}
}
return sortFields
}
func getQueryVarInt(r *http.Request, varName string, required bool, defaultValue int) (int, error) {
queryValues := r.URL.Query()
varValue := queryValues.Get(varName)
if varValue == "" && required {
return 0, fmt.Errorf("%v was not supplied in the request", varName)
} else if varValue == "" {
return defaultValue, nil
}
varInt, intErr := strconv.Atoi(varValue)
if intErr != nil {
return 0, fmt.Errorf("%v is not a valid number", varName)
}
return varInt, nil
}
func getURLParamInt(r *http.Request, varName string) (int, error) {
required := true
defaultValue := 0
paramStr := chi.URLParam(r, varName)
var err error
var paramInt int
if paramStr == "" && required {
return 0, fmt.Errorf("%v was not supplied in the request", varName)
} else if paramStr == "" {
return defaultValue, nil
}
if paramInt, err = strconv.Atoi(paramStr); err != nil {
return 0, fmt.Errorf("%v is not a valid number", varName)
}
return paramInt, nil
}
func getPagination(r *http.Request) (int, int, error) {
var err error
offset, err := getQueryVarInt(r, "offset", false, 0)
if err != nil {
return 0, 0, err
}
limit, err := getQueryVarInt(r, "limit", false, defaultLimit)
if err != nil {
return 0, 0, err
}
return offset, limit, nil
}

View File

@@ -0,0 +1,135 @@
package handler
import (
"encoding/json"
"fmt"
"net/http"
c "npm/internal/api/context"
h "npm/internal/api/http"
"npm/internal/api/middleware"
"npm/internal/entity/host"
"npm/internal/validator"
)
// GetHosts will return a list of Hosts
// Route: GET /hosts
func GetHosts() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
pageInfo, err := getPageInfoFromRequest(r)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
hosts, err := host.List(pageInfo, middleware.GetFiltersFromContext(r))
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, hosts)
}
}
}
// GetHost will return a single Host
// Route: GET /hosts/{hostID}
func GetHost() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var hostID int
if hostID, err = getURLParamInt(r, "hostID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
host, err := host.GetByID(hostID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, host)
}
}
}
// CreateHost will create a Host
// Route: POST /hosts
func CreateHost() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
var newHost host.Model
err := json.Unmarshal(bodyBytes, &newHost)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
// Get userID from token
userID, _ := r.Context().Value(c.UserIDCtxKey).(int)
newHost.UserID = userID
if err = validator.ValidateHost(newHost); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
if err = newHost.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, fmt.Sprintf("Unable to save Host: %s", err.Error()), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, newHost)
}
}
// UpdateHost ...
// Route: PUT /hosts/{hostID}
func UpdateHost() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var hostID int
if hostID, err = getURLParamInt(r, "hostID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
host, err := host.GetByID(hostID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
err := json.Unmarshal(bodyBytes, &host)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
if err = host.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, host)
}
}
}
// DeleteHost ...
// Route: DELETE /hosts/{hostID}
func DeleteHost() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var hostID int
if hostID, err = getURLParamInt(r, "hostID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
host, err := host.GetByID(hostID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, host.Delete())
}
}
}

View File

@@ -0,0 +1,14 @@
package handler
import (
"net/http"
h "npm/internal/api/http"
)
// NotAllowed is a json error handler for when method is not allowed
func NotAllowed() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
h.ResultErrorJSON(w, r, http.StatusNotFound, "Not allowed", nil)
}
}

View File

@@ -0,0 +1,65 @@
package handler
import (
"embed"
"errors"
"io"
"io/fs"
"mime"
"net/http"
"path/filepath"
"strings"
h "npm/internal/api/http"
)
//go:embed assets
var assets embed.FS
var assetsSub fs.FS
var errIsDir = errors.New("path is dir")
// NotFound is a json error handler for 404's and method not allowed.
// It also serves the react frontend as embedded files in the golang binary.
func NotFound() func(http.ResponseWriter, *http.Request) {
assetsSub, _ = fs.Sub(assets, "assets")
return func(w http.ResponseWriter, r *http.Request) {
path := strings.TrimLeft(r.URL.Path, "/")
if path == "" {
path = "index.html"
}
err := tryRead(assetsSub, path, w)
if err == errIsDir {
err = tryRead(assetsSub, "index.html", w)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusNotFound, "Not found", nil)
}
} else if err == nil {
return
}
h.ResultErrorJSON(w, r, http.StatusNotFound, "Not found", nil)
}
}
func tryRead(folder fs.FS, requestedPath string, w http.ResponseWriter) error {
f, err := folder.Open(requestedPath)
if err != nil {
return err
}
// nolint: errcheck
defer f.Close()
stat, _ := f.Stat()
if stat.IsDir() {
return errIsDir
}
contentType := mime.TypeByExtension(filepath.Ext(requestedPath))
w.Header().Set("Content-Type", contentType)
_, err = io.Copy(w, f)
return err
}

View File

@@ -0,0 +1,99 @@
package handler
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"npm/doc"
"npm/internal/api/schema"
"npm/internal/config"
"npm/internal/logger"
jsref "github.com/jc21/jsref"
"github.com/jc21/jsref/provider"
)
var swaggerSchema []byte
// Schema simply reads the swagger schema from disk and returns is raw
// Route: GET /schema
func Schema() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, string(getSchema()))
}
}
func getSchema() []byte {
if swaggerSchema == nil {
// nolint:gosec
swaggerSchema, _ = doc.SwaggerFiles.ReadFile("api.swagger.json")
// Replace {{VERSION}} with Config Version
swaggerSchema = []byte(strings.ReplaceAll(string(swaggerSchema), "{{VERSION}}", config.Version))
// Dereference the JSON Schema:
var schema interface{}
if err := json.Unmarshal(swaggerSchema, &schema); err != nil {
logger.Error("SwaggerUnmarshalError", err)
return nil
}
provider := provider.NewIoFS(doc.SwaggerFiles, "")
resolver := jsref.New()
err := resolver.AddProvider(provider)
if err != nil {
logger.Error("SchemaProviderError", err)
}
result, err := resolver.Resolve(schema, "", []jsref.Option{jsref.WithRecursiveResolution(true)}...)
if err != nil {
logger.Error("SwaggerResolveError", err)
} else {
var marshalErr error
swaggerSchema, marshalErr = json.MarshalIndent(result, "", " ")
if marshalErr != nil {
logger.Error("SwaggerMarshalError", err)
}
}
// End dereference
// Replace incoming schemas with those we actually use in code
swaggerSchema = replaceIncomingSchemas(swaggerSchema)
}
return swaggerSchema
}
func replaceIncomingSchemas(swaggerSchema []byte) []byte {
str := string(swaggerSchema)
// Remember to include the double quotes in the replacement!
str = strings.ReplaceAll(str, `"{{schema.SetAuth}}"`, schema.SetAuth())
str = strings.ReplaceAll(str, `"{{schema.GetToken}}"`, schema.GetToken())
str = strings.ReplaceAll(str, `"{{schema.CreateCertificateAuthority}}"`, schema.CreateCertificateAuthority())
str = strings.ReplaceAll(str, `"{{schema.UpdateCertificateAuthority}}"`, schema.UpdateCertificateAuthority())
str = strings.ReplaceAll(str, `"{{schema.CreateCertificate}}"`, schema.CreateCertificate())
str = strings.ReplaceAll(str, `"{{schema.UpdateCertificate}}"`, schema.UpdateCertificate(""))
str = strings.ReplaceAll(str, `"{{schema.CreateSetting}}"`, schema.CreateSetting())
str = strings.ReplaceAll(str, `"{{schema.UpdateSetting}}"`, schema.UpdateSetting())
str = strings.ReplaceAll(str, `"{{schema.CreateUser}}"`, schema.CreateUser())
str = strings.ReplaceAll(str, `"{{schema.UpdateUser}}"`, schema.UpdateUser())
str = strings.ReplaceAll(str, `"{{schema.CreateHost}}"`, schema.CreateHost())
str = strings.ReplaceAll(str, `"{{schema.UpdateHost}}"`, schema.UpdateHost())
str = strings.ReplaceAll(str, `"{{schema.CreateStream}}"`, schema.CreateStream())
str = strings.ReplaceAll(str, `"{{schema.UpdateStream}}"`, schema.UpdateStream())
str = strings.ReplaceAll(str, `"{{schema.CreateDNSProvider}}"`, schema.CreateDNSProvider())
str = strings.ReplaceAll(str, `"{{schema.UpdateDNSProvider}}"`, schema.UpdateDNSProvider())
return []byte(str)
}

View File

@@ -0,0 +1,98 @@
package handler
import (
"encoding/json"
"fmt"
"net/http"
c "npm/internal/api/context"
h "npm/internal/api/http"
"npm/internal/api/middleware"
"npm/internal/entity/setting"
"github.com/go-chi/chi"
)
// GetSettings will return a list of Settings
// Route: GET /settings
func GetSettings() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
pageInfo, err := getPageInfoFromRequest(r)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
settings, err := setting.List(pageInfo, middleware.GetFiltersFromContext(r))
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, settings)
}
}
}
// GetSetting will return a single Setting
// Route: GET /settings/{name}
func GetSetting() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
sett, err := setting.GetByName(name)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, sett)
}
}
}
// CreateSetting will create a Setting
// Route: POST /settings
func CreateSetting() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
var newSetting setting.Model
err := json.Unmarshal(bodyBytes, &newSetting)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
if err = newSetting.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, fmt.Sprintf("Unable to save Setting: %s", err.Error()), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, newSetting)
}
}
// UpdateSetting ...
// Route: PUT /settings/{name}
func UpdateSetting() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
settingName := chi.URLParam(r, "name")
setting, err := setting.GetByName(settingName)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
err := json.Unmarshal(bodyBytes, &setting)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
if err = setting.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, setting)
}
}
}

View File

@@ -0,0 +1,129 @@
package handler
import (
"encoding/json"
"fmt"
"net/http"
c "npm/internal/api/context"
h "npm/internal/api/http"
"npm/internal/api/middleware"
"npm/internal/entity/stream"
)
// GetStreams will return a list of Streams
// Route: GET /hosts/streams
func GetStreams() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
pageInfo, err := getPageInfoFromRequest(r)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
hosts, err := stream.List(pageInfo, middleware.GetFiltersFromContext(r))
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, hosts)
}
}
}
// GetStream will return a single Streams
// Route: GET /hosts/streams/{hostID}
func GetStream() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var hostID int
if hostID, err = getURLParamInt(r, "hostID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
host, err := stream.GetByID(hostID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, host)
}
}
}
// CreateStream will create a Stream
// Route: POST /hosts/steams
func CreateStream() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
var newHost stream.Model
err := json.Unmarshal(bodyBytes, &newHost)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
// Get userID from token
userID, _ := r.Context().Value(c.UserIDCtxKey).(int)
newHost.UserID = userID
if err = newHost.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, fmt.Sprintf("Unable to save Stream: %s", err.Error()), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, newHost)
}
}
// UpdateStream ...
// Route: PUT /hosts/streams/{hostID}
func UpdateStream() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var hostID int
if hostID, err = getURLParamInt(r, "hostID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
host, err := stream.GetByID(hostID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
err := json.Unmarshal(bodyBytes, &host)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
if err = host.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
h.ResultResponseJSON(w, r, http.StatusOK, host)
}
}
}
// DeleteStream ...
// Route: DELETE /hosts/streams/{hostID}
func DeleteStream() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var err error
var hostID int
if hostID, err = getURLParamInt(r, "hostID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
host, err := stream.GetByID(hostID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, host.Delete())
}
}
}

View File

@@ -0,0 +1,77 @@
package handler
import (
"encoding/json"
"net/http"
h "npm/internal/api/http"
c "npm/internal/api/context"
"npm/internal/entity/auth"
"npm/internal/entity/user"
njwt "npm/internal/jwt"
)
// tokenPayload is the structure we expect from a incoming login request
type tokenPayload struct {
Type string `json:"type"`
Identity string `json:"identity"`
Secret string `json:"secret"`
}
// NewToken Also known as a Login, requesting a new token with credentials
// Route: POST /tokens
func NewToken() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
// Read the bytes from the body
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
var payload tokenPayload
err := json.Unmarshal(bodyBytes, &payload)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
// Find user
userObj, userErr := user.GetByEmail(payload.Identity)
if userErr != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, userErr.Error(), nil)
return
}
// Get Auth
authObj, authErr := auth.GetByUserIDType(userObj.ID, payload.Type)
if userErr != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, authErr.Error(), nil)
return
}
// Verify Auth
validateErr := authObj.ValidateSecret(payload.Secret)
if validateErr != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, validateErr.Error(), nil)
return
}
if response, err := njwt.Generate(&userObj); err != nil {
h.ResultErrorJSON(w, r, http.StatusInternalServerError, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, response)
}
}
}
// RefreshToken an existing token by given them a new one with the same claims
// Route: GET /tokens
func RefreshToken() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
// TODO: Use your own methods to verify an existing user is
// able to refresh their token and then give them a new one
userObj, _ := user.GetByEmail("jc@jc21.com")
if response, err := njwt.Generate(&userObj); err != nil {
h.ResultErrorJSON(w, r, http.StatusInternalServerError, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, response)
}
}
}

View File

@@ -0,0 +1,206 @@
package handler
import (
"encoding/json"
"net/http"
c "npm/internal/api/context"
h "npm/internal/api/http"
"npm/internal/api/middleware"
"npm/internal/config"
"npm/internal/entity/auth"
"npm/internal/entity/user"
"npm/internal/errors"
"npm/internal/logger"
"github.com/go-chi/chi"
)
// GetUsers ...
// Route: GET /users
func GetUsers() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
pageInfo, err := getPageInfoFromRequest(r)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
users, err := user.List(pageInfo, middleware.GetFiltersFromContext(r))
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, users)
}
}
}
// GetUser ...
// Route: GET /users/{userID}
func GetUser() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
userID, _, userIDErr := getUserIDFromRequest(r)
if userIDErr != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, userIDErr.Error(), nil)
return
}
user, err := user.GetByID(userID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, user)
}
}
}
// UpdateUser ...
// Route: PUT /users/{userID}
func UpdateUser() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
userID, self, userIDErr := getUserIDFromRequest(r)
if userIDErr != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, userIDErr.Error(), nil)
return
}
user, err := user.GetByID(userID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
err := json.Unmarshal(bodyBytes, &user)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
if user.IsDisabled && self {
h.ResultErrorJSON(w, r, http.StatusBadRequest, "You cannot disable yourself!", nil)
return
}
if err = user.Save(); err != nil {
if err == errors.ErrDuplicateEmailUser {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultErrorJSON(w, r, http.StatusInternalServerError, "Unable to save User", nil)
}
return
}
h.ResultResponseJSON(w, r, http.StatusOK, user)
}
}
}
// DeleteUser ...
// Route: DELETE /users/{userID}
func DeleteUser() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var userID int
var err error
if userID, err = getURLParamInt(r, "userID"); err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
return
}
myUserID, _ := r.Context().Value(c.UserIDCtxKey).(int)
if myUserID == userID {
h.ResultErrorJSON(w, r, http.StatusBadRequest, "You cannot delete yourself!", nil)
return
}
user, err := user.GetByID(userID)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultResponseJSON(w, r, http.StatusOK, user.Delete())
}
}
}
// CreateUser ...
// Route: POST /users
func CreateUser() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
bodyBytes, _ := r.Context().Value(c.BodyCtxKey).([]byte)
var newUser user.Model
err := json.Unmarshal(bodyBytes, &newUser)
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, h.ErrInvalidPayload.Error(), nil)
return
}
if err = newUser.Save(); err != nil {
if err == errors.ErrDuplicateEmailUser {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
h.ResultErrorJSON(w, r, http.StatusInternalServerError, "Unable to save User", nil)
}
return
}
// newUser has been saved, now save their auth
if newUser.Auth.Secret != "" && newUser.Auth.ID == 0 {
newUser.Auth.UserID = newUser.ID
if newUser.Auth.Type == auth.TypePassword {
err = newUser.Auth.SetPassword(newUser.Auth.Secret)
if err != nil {
logger.Error("SetPasswordError", err)
}
}
if err = newUser.Auth.Save(); err != nil {
h.ResultErrorJSON(w, r, http.StatusInternalServerError, "Unable to save Authentication for User", nil)
return
}
newUser.Auth.Secret = ""
}
if !config.IsSetup {
config.IsSetup = true
logger.Info("A new user was created, leaving Setup Mode")
}
h.ResultResponseJSON(w, r, http.StatusOK, newUser)
}
}
// DeleteUsers is only available in debug mode for cypress tests
// Route: DELETE /users
func DeleteUsers() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
err := user.DeleteAll()
if err != nil {
h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil)
} else {
// also change setup to true
config.IsSetup = false
logger.Info("Users have been wiped, entering Setup Mode")
h.ResultResponseJSON(w, r, http.StatusOK, true)
}
}
}
func getUserIDFromRequest(r *http.Request) (int, bool, error) {
userIDstr := chi.URLParam(r, "userID")
var userID int
self := false
if userIDstr == "me" {
// Get user id from Token
userID, _ = r.Context().Value(c.UserIDCtxKey).(int)
self = true
} else {
var userIDerr error
if userID, userIDerr = getURLParamInt(r, "userID"); userIDerr != nil {
return 0, false, userIDerr
}
}
return userID, self, nil
}