mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
32 path-segment reads across users, service accounts, auth domains and roles
stop naming gorilla and read through coretypes.Param. Which router matched the
route is not a fact these handlers should know — the value they need is the
segment.
Each package gains the test it never had: a REAL route template on a real
router, distinct uuids in each segment, and an assertion on which value reached
the store. /users/{id}/roles/{roleId}, /service_accounts/{id}/roles/{rid} and
/service_accounts/{id}/keys/{fid} each carry TWO segments, and a handler that
reads the wrong name still compiles and still answers 2xx — it just acts on the
wrong resource. The self-modification guard in UpdateUser is pinned by body,
because it compares the PATH segment to the caller: read the wrong name and it
compares "" and lets the edit through.
Registration is untouched — {id} literals, *mux.Router and the gates stay put
for the one atomic commit that converts them.
Co-authored-by: Hanzo Dev <[email protected]>
455 lines
10 KiB
Go
455 lines
10 KiB
Go
package implserviceaccount
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/hanzoai/o11y/pkg/http/binding"
|
|
"github.com/hanzoai/o11y/pkg/http/render"
|
|
"github.com/hanzoai/o11y/pkg/modules/serviceaccount"
|
|
"github.com/hanzoai/o11y/pkg/types"
|
|
"github.com/hanzoai/o11y/pkg/types/authtypes"
|
|
"github.com/hanzoai/o11y/pkg/types/coretypes"
|
|
"github.com/hanzoai/o11y/pkg/types/serviceaccounttypes"
|
|
"github.com/hanzoai/o11y/pkg/valuer"
|
|
)
|
|
|
|
type handler struct {
|
|
module serviceaccount.Module
|
|
}
|
|
|
|
func NewHandler(module serviceaccount.Module) serviceaccount.Handler {
|
|
return &handler{module: module}
|
|
}
|
|
|
|
func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
req := new(serviceaccounttypes.PostableServiceAccount)
|
|
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
serviceAccount := serviceaccounttypes.NewServiceAccount(req.Name, handler.module.Config().Email.Domain, serviceaccounttypes.ServiceAccountStatusActive, valuer.MustNewUUID(claims.OrgID))
|
|
err = handler.module.Create(ctx, valuer.MustNewUUID(claims.OrgID), serviceAccount)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusCreated, types.Identifiable{ID: serviceAccount.ID})
|
|
}
|
|
|
|
func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
id, err := valuer.NewUUID(coretypes.Param(r, "id"))
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
serviceAccountWithRoles, err := handler.module.GetWithRoles(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusOK, serviceAccountWithRoles)
|
|
}
|
|
|
|
func (handler *handler) GetMe(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
id, err := valuer.NewUUID(claims.ServiceAccountID)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
serviceAccountWithRoles, err := handler.module.GetWithRoles(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusOK, serviceAccountWithRoles)
|
|
}
|
|
|
|
func (handler *handler) GetRoles(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
id, err := valuer.NewUUID(coretypes.Param(r, "id"))
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
serviceAccount, err := handler.module.GetWithRoles(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusOK, serviceAccount.GetRoles())
|
|
}
|
|
|
|
func (handler *handler) List(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
serviceAccounts, err := handler.module.List(ctx, valuer.MustNewUUID(claims.OrgID))
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusOK, serviceAccounts)
|
|
}
|
|
|
|
func (handler *handler) Update(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
id, err := valuer.NewUUID(coretypes.Param(r, "id"))
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
req := new(serviceaccounttypes.UpdatableServiceAccount)
|
|
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
serviceAccount, err := handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
err = serviceAccount.Update(req.Name)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
err = handler.module.Update(ctx, valuer.MustNewUUID(claims.OrgID), serviceAccount)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (handler *handler) UpdateMe(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
id, err := valuer.NewUUID(claims.ServiceAccountID)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
req := new(serviceaccounttypes.UpdatableServiceAccount)
|
|
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
serviceAccount, err := handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
err = serviceAccount.Update(req.Name)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
err = handler.module.Update(ctx, valuer.MustNewUUID(claims.OrgID), serviceAccount)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (handler *handler) SetRole(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
id, err := valuer.NewUUID(coretypes.Param(r, "id"))
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
req := new(serviceaccounttypes.PostableServiceAccountRole)
|
|
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
err = handler.module.SetRole(ctx, valuer.MustNewUUID(claims.OrgID), id, req.ID)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (handler *handler) DeleteRole(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
id, err := valuer.NewUUID(coretypes.Param(r, "id"))
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
roleID, err := valuer.NewUUID(coretypes.Param(r, "rid"))
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
err = handler.module.DeleteRole(ctx, valuer.MustNewUUID(claims.OrgID), id, roleID)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
id, err := valuer.NewUUID(coretypes.Param(r, "id"))
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
err = handler.module.Delete(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (handler *handler) CreateFactorAPIKey(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
id, err := valuer.NewUUID(coretypes.Param(r, "id"))
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
req := new(serviceaccounttypes.PostableFactorAPIKey)
|
|
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
// this takes care of checking the existence of service account and the org constraint.
|
|
serviceAccount, err := handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
factorAPIKey, err := serviceAccount.NewFactorAPIKey(req.Name, req.ExpiresAt)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
err = handler.module.CreateFactorAPIKey(ctx, factorAPIKey)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusCreated, serviceaccounttypes.NewGettableFactorAPIKeyWithKey(factorAPIKey.ID, factorAPIKey.Key))
|
|
}
|
|
|
|
func (handler *handler) ListFactorAPIKey(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
id, err := valuer.NewUUID(coretypes.Param(r, "id"))
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
serviceAccount, err := handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
factorAPIKeys, err := handler.module.ListFactorAPIKey(ctx, serviceAccount.ID)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusOK, serviceaccounttypes.NewGettableFactorAPIKeys(factorAPIKeys))
|
|
}
|
|
|
|
func (handler *handler) UpdateFactorAPIKey(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
id, err := valuer.NewUUID(coretypes.Param(r, "id"))
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
factorAPIKeyID, err := valuer.NewUUID(coretypes.Param(r, "fid"))
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
req := new(serviceaccounttypes.UpdatableFactorAPIKey)
|
|
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
serviceAccount, err := handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
factorAPIKey, err := handler.module.GetFactorAPIKey(ctx, serviceAccount.ID, factorAPIKeyID)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
err = factorAPIKey.Update(req.Name, req.ExpiresAt)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
err = handler.module.UpdateFactorAPIKey(ctx, valuer.MustNewUUID(claims.OrgID), serviceAccount.ID, factorAPIKey)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (handler *handler) RevokeFactorAPIKey(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
id, err := valuer.NewUUID(coretypes.Param(r, "id"))
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
factorAPIKeyID, err := valuer.NewUUID(coretypes.Param(r, "fid"))
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
serviceAccount, err := handler.module.Get(ctx, valuer.MustNewUUID(claims.OrgID), id)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
err = handler.module.RevokeFactorAPIKey(ctx, serviceAccount.ID, factorAPIKeyID)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|