forked from TrueCloudLab/certificates
authority/admin: refactored to support api/render.Error
This commit is contained in:
parent
6636e87fc7
commit
098c2e1134
4 changed files with 45 additions and 43 deletions
|
@ -6,10 +6,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
"github.com/smallstep/certificates/api"
|
|
||||||
|
"go.step.sm/linkedca"
|
||||||
|
|
||||||
|
"github.com/smallstep/certificates/api/render"
|
||||||
"github.com/smallstep/certificates/authority/admin"
|
"github.com/smallstep/certificates/authority/admin"
|
||||||
"github.com/smallstep/certificates/authority/provisioner"
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
"go.step.sm/linkedca"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -44,11 +46,11 @@ func (h *Handler) requireEABEnabled(next nextHTTP) nextHTTP {
|
||||||
provName := chi.URLParam(r, "provisionerName")
|
provName := chi.URLParam(r, "provisionerName")
|
||||||
eabEnabled, prov, err := h.provisionerHasEABEnabled(ctx, provName)
|
eabEnabled, prov, err := h.provisionerHasEABEnabled(ctx, provName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.WriteError(w, err)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !eabEnabled {
|
if !eabEnabled {
|
||||||
api.WriteError(w, admin.NewError(admin.ErrorBadRequestType, "ACME EAB not enabled for provisioner %s", prov.GetName()))
|
render.Error(w, admin.NewError(admin.ErrorBadRequestType, "ACME EAB not enabled for provisioner %s", prov.GetName()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
ctx = context.WithValue(ctx, provisionerContextKey, prov)
|
||||||
|
@ -101,15 +103,15 @@ func NewACMEAdminResponder() *ACMEAdminResponder {
|
||||||
|
|
||||||
// GetExternalAccountKeys writes the response for the EAB keys GET endpoint
|
// GetExternalAccountKeys writes the response for the EAB keys GET endpoint
|
||||||
func (h *ACMEAdminResponder) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request) {
|
func (h *ACMEAdminResponder) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request) {
|
||||||
api.WriteError(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
|
render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateExternalAccountKey writes the response for the EAB key POST endpoint
|
// CreateExternalAccountKey writes the response for the EAB key POST endpoint
|
||||||
func (h *ACMEAdminResponder) CreateExternalAccountKey(w http.ResponseWriter, r *http.Request) {
|
func (h *ACMEAdminResponder) CreateExternalAccountKey(w http.ResponseWriter, r *http.Request) {
|
||||||
api.WriteError(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
|
render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteExternalAccountKey writes the response for the EAB key DELETE endpoint
|
// DeleteExternalAccountKey writes the response for the EAB key DELETE endpoint
|
||||||
func (h *ACMEAdminResponder) DeleteExternalAccountKey(w http.ResponseWriter, r *http.Request) {
|
func (h *ACMEAdminResponder) DeleteExternalAccountKey(w http.ResponseWriter, r *http.Request) {
|
||||||
api.WriteError(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
|
render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,7 +86,7 @@ func (h *Handler) GetAdmin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
adm, ok := h.auth.LoadAdminByID(id)
|
adm, ok := h.auth.LoadAdminByID(id)
|
||||||
if !ok {
|
if !ok {
|
||||||
api.WriteError(w, admin.NewError(admin.ErrorNotFoundType,
|
render.Error(w, admin.NewError(admin.ErrorNotFoundType,
|
||||||
"admin %s not found", id))
|
"admin %s not found", id))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -97,14 +97,14 @@ func (h *Handler) GetAdmin(w http.ResponseWriter, r *http.Request) {
|
||||||
func (h *Handler) GetAdmins(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) GetAdmins(w http.ResponseWriter, r *http.Request) {
|
||||||
cursor, limit, err := api.ParseCursor(r)
|
cursor, limit, err := api.ParseCursor(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.WriteError(w, admin.WrapError(admin.ErrorBadRequestType, err,
|
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err,
|
||||||
"error parsing cursor and limit from query params"))
|
"error parsing cursor and limit from query params"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
admins, nextCursor, err := h.auth.GetAdmins(cursor, limit)
|
admins, nextCursor, err := h.auth.GetAdmins(cursor, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.WriteError(w, admin.WrapErrorISE(err, "error retrieving paginated admins"))
|
render.Error(w, admin.WrapErrorISE(err, "error retrieving paginated admins"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
render.JSON(w, &GetAdminsResponse{
|
render.JSON(w, &GetAdminsResponse{
|
||||||
|
@ -117,18 +117,18 @@ func (h *Handler) GetAdmins(w http.ResponseWriter, r *http.Request) {
|
||||||
func (h *Handler) CreateAdmin(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) CreateAdmin(w http.ResponseWriter, r *http.Request) {
|
||||||
var body CreateAdminRequest
|
var body CreateAdminRequest
|
||||||
if err := read.JSON(r.Body, &body); err != nil {
|
if err := read.JSON(r.Body, &body); err != nil {
|
||||||
api.WriteError(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
|
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := body.Validate(); err != nil {
|
if err := body.Validate(); err != nil {
|
||||||
api.WriteError(w, err)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p, err := h.auth.LoadProvisionerByName(body.Provisioner)
|
p, err := h.auth.LoadProvisionerByName(body.Provisioner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", body.Provisioner))
|
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", body.Provisioner))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
adm := &linkedca.Admin{
|
adm := &linkedca.Admin{
|
||||||
|
@ -138,7 +138,7 @@ func (h *Handler) CreateAdmin(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
// Store to authority collection.
|
// Store to authority collection.
|
||||||
if err := h.auth.StoreAdmin(r.Context(), adm, p); err != nil {
|
if err := h.auth.StoreAdmin(r.Context(), adm, p); err != nil {
|
||||||
api.WriteError(w, admin.WrapErrorISE(err, "error storing admin"))
|
render.Error(w, admin.WrapErrorISE(err, "error storing admin"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ func (h *Handler) DeleteAdmin(w http.ResponseWriter, r *http.Request) {
|
||||||
id := chi.URLParam(r, "id")
|
id := chi.URLParam(r, "id")
|
||||||
|
|
||||||
if err := h.auth.RemoveAdmin(r.Context(), id); err != nil {
|
if err := h.auth.RemoveAdmin(r.Context(), id); err != nil {
|
||||||
api.WriteError(w, admin.WrapErrorISE(err, "error deleting admin %s", id))
|
render.Error(w, admin.WrapErrorISE(err, "error deleting admin %s", id))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,12 +161,12 @@ func (h *Handler) DeleteAdmin(w http.ResponseWriter, r *http.Request) {
|
||||||
func (h *Handler) UpdateAdmin(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) UpdateAdmin(w http.ResponseWriter, r *http.Request) {
|
||||||
var body UpdateAdminRequest
|
var body UpdateAdminRequest
|
||||||
if err := read.JSON(r.Body, &body); err != nil {
|
if err := read.JSON(r.Body, &body); err != nil {
|
||||||
api.WriteError(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
|
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := body.Validate(); err != nil {
|
if err := body.Validate(); err != nil {
|
||||||
api.WriteError(w, err)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ func (h *Handler) UpdateAdmin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
adm, err := h.auth.UpdateAdmin(r.Context(), id, &linkedca.Admin{Type: body.Type})
|
adm, err := h.auth.UpdateAdmin(r.Context(), id, &linkedca.Admin{Type: body.Type})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.WriteError(w, admin.WrapErrorISE(err, "error updating admin %s", id))
|
render.Error(w, admin.WrapErrorISE(err, "error updating admin %s", id))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/smallstep/certificates/api"
|
"github.com/smallstep/certificates/api/render"
|
||||||
"github.com/smallstep/certificates/authority/admin"
|
"github.com/smallstep/certificates/authority/admin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ type nextHTTP = func(http.ResponseWriter, *http.Request)
|
||||||
func (h *Handler) requireAPIEnabled(next nextHTTP) nextHTTP {
|
func (h *Handler) requireAPIEnabled(next nextHTTP) nextHTTP {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
if !h.auth.IsAdminAPIEnabled() {
|
if !h.auth.IsAdminAPIEnabled() {
|
||||||
api.WriteError(w, admin.NewError(admin.ErrorNotImplementedType,
|
render.Error(w, admin.NewError(admin.ErrorNotImplementedType,
|
||||||
"administration API not enabled"))
|
"administration API not enabled"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -28,14 +28,14 @@ func (h *Handler) extractAuthorizeTokenAdmin(next nextHTTP) nextHTTP {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
tok := r.Header.Get("Authorization")
|
tok := r.Header.Get("Authorization")
|
||||||
if tok == "" {
|
if tok == "" {
|
||||||
api.WriteError(w, admin.NewError(admin.ErrorUnauthorizedType,
|
render.Error(w, admin.NewError(admin.ErrorUnauthorizedType,
|
||||||
"missing authorization header token"))
|
"missing authorization header token"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
adm, err := h.auth.AuthorizeAdminToken(r, tok)
|
adm, err := h.auth.AuthorizeAdminToken(r, tok)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.WriteError(w, err)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,19 +35,19 @@ func (h *Handler) GetProvisioner(w http.ResponseWriter, r *http.Request) {
|
||||||
)
|
)
|
||||||
if len(id) > 0 {
|
if len(id) > 0 {
|
||||||
if p, err = h.auth.LoadProvisionerByID(id); err != nil {
|
if p, err = h.auth.LoadProvisionerByID(id); err != nil {
|
||||||
api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", id))
|
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", id))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if p, err = h.auth.LoadProvisionerByName(name); err != nil {
|
if p, err = h.auth.LoadProvisionerByName(name); err != nil {
|
||||||
api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", name))
|
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", name))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prov, err := h.adminDB.GetProvisioner(ctx, p.GetID())
|
prov, err := h.adminDB.GetProvisioner(ctx, p.GetID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.WriteError(w, err)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
render.ProtoJSON(w, prov)
|
render.ProtoJSON(w, prov)
|
||||||
|
@ -57,14 +57,14 @@ func (h *Handler) GetProvisioner(w http.ResponseWriter, r *http.Request) {
|
||||||
func (h *Handler) GetProvisioners(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) GetProvisioners(w http.ResponseWriter, r *http.Request) {
|
||||||
cursor, limit, err := api.ParseCursor(r)
|
cursor, limit, err := api.ParseCursor(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.WriteError(w, admin.WrapError(admin.ErrorBadRequestType, err,
|
render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err,
|
||||||
"error parsing cursor and limit from query params"))
|
"error parsing cursor and limit from query params"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p, next, err := h.auth.GetProvisioners(cursor, limit)
|
p, next, err := h.auth.GetProvisioners(cursor, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.WriteError(w, errs.InternalServerErr(err))
|
render.Error(w, errs.InternalServerErr(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
render.JSON(w, &GetProvisionersResponse{
|
render.JSON(w, &GetProvisionersResponse{
|
||||||
|
@ -77,18 +77,18 @@ func (h *Handler) GetProvisioners(w http.ResponseWriter, r *http.Request) {
|
||||||
func (h *Handler) CreateProvisioner(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) CreateProvisioner(w http.ResponseWriter, r *http.Request) {
|
||||||
var prov = new(linkedca.Provisioner)
|
var prov = new(linkedca.Provisioner)
|
||||||
if err := read.ProtoJSON(r.Body, prov); err != nil {
|
if err := read.ProtoJSON(r.Body, prov); err != nil {
|
||||||
api.WriteError(w, err)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Validate inputs
|
// TODO: Validate inputs
|
||||||
if err := authority.ValidateClaims(prov.Claims); err != nil {
|
if err := authority.ValidateClaims(prov.Claims); err != nil {
|
||||||
api.WriteError(w, err)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.auth.StoreProvisioner(r.Context(), prov); err != nil {
|
if err := h.auth.StoreProvisioner(r.Context(), prov); err != nil {
|
||||||
api.WriteError(w, admin.WrapErrorISE(err, "error storing provisioner %s", prov.Name))
|
render.Error(w, admin.WrapErrorISE(err, "error storing provisioner %s", prov.Name))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
render.ProtoJSONStatus(w, prov, http.StatusCreated)
|
render.ProtoJSONStatus(w, prov, http.StatusCreated)
|
||||||
|
@ -105,18 +105,18 @@ func (h *Handler) DeleteProvisioner(w http.ResponseWriter, r *http.Request) {
|
||||||
)
|
)
|
||||||
if len(id) > 0 {
|
if len(id) > 0 {
|
||||||
if p, err = h.auth.LoadProvisionerByID(id); err != nil {
|
if p, err = h.auth.LoadProvisionerByID(id); err != nil {
|
||||||
api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", id))
|
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", id))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if p, err = h.auth.LoadProvisionerByName(name); err != nil {
|
if p, err = h.auth.LoadProvisionerByName(name); err != nil {
|
||||||
api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", name))
|
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", name))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.auth.RemoveProvisioner(r.Context(), p.GetID()); err != nil {
|
if err := h.auth.RemoveProvisioner(r.Context(), p.GetID()); err != nil {
|
||||||
api.WriteError(w, admin.WrapErrorISE(err, "error removing provisioner %s", p.GetName()))
|
render.Error(w, admin.WrapErrorISE(err, "error removing provisioner %s", p.GetName()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,52 +127,52 @@ func (h *Handler) DeleteProvisioner(w http.ResponseWriter, r *http.Request) {
|
||||||
func (h *Handler) UpdateProvisioner(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) UpdateProvisioner(w http.ResponseWriter, r *http.Request) {
|
||||||
var nu = new(linkedca.Provisioner)
|
var nu = new(linkedca.Provisioner)
|
||||||
if err := read.ProtoJSON(r.Body, nu); err != nil {
|
if err := read.ProtoJSON(r.Body, nu); err != nil {
|
||||||
api.WriteError(w, err)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
name := chi.URLParam(r, "name")
|
name := chi.URLParam(r, "name")
|
||||||
_old, err := h.auth.LoadProvisionerByName(name)
|
_old, err := h.auth.LoadProvisionerByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner from cached configuration '%s'", name))
|
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner from cached configuration '%s'", name))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
old, err := h.adminDB.GetProvisioner(r.Context(), _old.GetID())
|
old, err := h.adminDB.GetProvisioner(r.Context(), _old.GetID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner from db '%s'", _old.GetID()))
|
render.Error(w, admin.WrapErrorISE(err, "error loading provisioner from db '%s'", _old.GetID()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if nu.Id != old.Id {
|
if nu.Id != old.Id {
|
||||||
api.WriteError(w, admin.NewErrorISE("cannot change provisioner ID"))
|
render.Error(w, admin.NewErrorISE("cannot change provisioner ID"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if nu.Type != old.Type {
|
if nu.Type != old.Type {
|
||||||
api.WriteError(w, admin.NewErrorISE("cannot change provisioner type"))
|
render.Error(w, admin.NewErrorISE("cannot change provisioner type"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if nu.AuthorityId != old.AuthorityId {
|
if nu.AuthorityId != old.AuthorityId {
|
||||||
api.WriteError(w, admin.NewErrorISE("cannot change provisioner authorityID"))
|
render.Error(w, admin.NewErrorISE("cannot change provisioner authorityID"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !nu.CreatedAt.AsTime().Equal(old.CreatedAt.AsTime()) {
|
if !nu.CreatedAt.AsTime().Equal(old.CreatedAt.AsTime()) {
|
||||||
api.WriteError(w, admin.NewErrorISE("cannot change provisioner createdAt"))
|
render.Error(w, admin.NewErrorISE("cannot change provisioner createdAt"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !nu.DeletedAt.AsTime().Equal(old.DeletedAt.AsTime()) {
|
if !nu.DeletedAt.AsTime().Equal(old.DeletedAt.AsTime()) {
|
||||||
api.WriteError(w, admin.NewErrorISE("cannot change provisioner deletedAt"))
|
render.Error(w, admin.NewErrorISE("cannot change provisioner deletedAt"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Validate inputs
|
// TODO: Validate inputs
|
||||||
if err := authority.ValidateClaims(nu.Claims); err != nil {
|
if err := authority.ValidateClaims(nu.Claims); err != nil {
|
||||||
api.WriteError(w, err)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.auth.UpdateProvisioner(r.Context(), nu); err != nil {
|
if err := h.auth.UpdateProvisioner(r.Context(), nu); err != nil {
|
||||||
api.WriteError(w, err)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
render.ProtoJSON(w, nu)
|
render.ProtoJSON(w, nu)
|
||||||
|
|
Loading…
Reference in a new issue