API keys for server scope: edges, server alerts and orgs can be scripted
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
39659f0144
commit
1c2a5b71bd
6 files changed
+232
-9
No files matched your search
@@ -29,7 +29,9 @@ func (s *Server) createOrg(w http.ResponseWriter, r *http.Request, p *principal)
|
||||
}
|
||||
s.mu.Lock()
|
||||
org, adminRole := s.newOrgLocked(in.Name, in.AllowLocal)
|
||||
s.st.Members = append(s.st.Members, &Member{UserID: p.User.ID, OrgID: org.ID, RoleID: adminRole.ID})
|
||||
if p.User.ID != "" { // a server key belongs to no user
|
||||
s.st.Members = append(s.st.Members, &Member{UserID: p.User.ID, OrgID: org.ID, RoleID: adminRole.ID})
|
||||
}
|
||||
s.save()
|
||||
o := *org
|
||||
s.mu.Unlock()
|
||||
@@ -423,7 +425,8 @@ func (s *Server) listKeys(w http.ResponseWriter, r *http.Request, c *orgCtx) {
|
||||
writeJSON(w, 200, out)
|
||||
}
|
||||
|
||||
func (s *Server) createKey(w http.ResponseWriter, r *http.Request, c *orgCtx) {
|
||||
// issueKey stores a new key for the org ("" = a server key) and returns its ID and full token.
|
||||
func (s *Server) issueKey(w http.ResponseWriter, r *http.Request, org, actor string) {
|
||||
var in struct{ Name string }
|
||||
if !decode(w, r, &in) {
|
||||
return
|
||||
@@ -435,11 +438,82 @@ func (s *Server) createKey(w http.ResponseWriter, r *http.Request, c *orgCtx) {
|
||||
}
|
||||
id, secret := randHex(4), randHex(24)
|
||||
s.mu.Lock()
|
||||
s.st.APIKeys = append(s.st.APIKeys, &APIKey{ID: id, OrgID: c.Org.ID, Name: in.Name, Hash: sha(secret), Created: time.Now()})
|
||||
s.st.APIKeys = append(s.st.APIKeys, &APIKey{ID: id, OrgID: org, Name: in.Name, Hash: sha(secret), Created: time.Now()})
|
||||
s.save()
|
||||
s.mu.Unlock()
|
||||
s.audit(c.Org.ID, c.Actor, "apikey.create", in.Name)
|
||||
writeJSON(w, 200, map[string]string{"key": "hhk_" + id + "_" + secret})
|
||||
s.audit(org, actor, "apikey.create", in.Name)
|
||||
writeJSON(w, 200, map[string]string{"id": id, "key": "hhk_" + id + "_" + secret})
|
||||
}
|
||||
|
||||
func (s *Server) createKey(w http.ResponseWriter, r *http.Request, c *orgCtx) {
|
||||
s.issueKey(w, r, c.Org.ID, c.Actor)
|
||||
}
|
||||
|
||||
// Server keys manage edges, server-level alerts and organizations, so only a signed-in
|
||||
// server administrator may mint, list or revoke them (a key can't make more keys).
|
||||
|
||||
func (s *Server) withServerAdminUser(fn func(http.ResponseWriter, *http.Request, *principal)) http.HandlerFunc {
|
||||
return s.withUser(func(w http.ResponseWriter, r *http.Request, p *principal) {
|
||||
if !p.User.ServerAdmin {
|
||||
fail(w, 403, errors.New("server administrators only"))
|
||||
return
|
||||
}
|
||||
fn(w, r, p)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) listServerKeys(w http.ResponseWriter, r *http.Request, p *principal) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
out := []APIKey{}
|
||||
for _, k := range s.st.APIKeys {
|
||||
if k.OrgID == "" {
|
||||
cp := *k
|
||||
cp.Hash = ""
|
||||
out = append(out, cp)
|
||||
}
|
||||
}
|
||||
writeJSON(w, 200, out)
|
||||
}
|
||||
|
||||
func (s *Server) createServerKey(w http.ResponseWriter, r *http.Request, p *principal) {
|
||||
s.issueKey(w, r, "", p.User.Email)
|
||||
}
|
||||
|
||||
func (s *Server) deleteServerKey(w http.ResponseWriter, r *http.Request, p *principal) {
|
||||
id := r.PathValue("id")
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.st.APIKeys = filter(s.st.APIKeys, func(k *APIKey) bool { return !(k.ID == id && k.OrgID == "") })
|
||||
s.save()
|
||||
s.audit("", p.User.Email, "apikey.delete", id)
|
||||
ok(w)
|
||||
}
|
||||
|
||||
// listOrgs shows the organizations the caller can reach: a server key sees all of them,
|
||||
// an org key its own.
|
||||
func (s *Server) listOrgs(w http.ResponseWriter, r *http.Request) {
|
||||
p := s.principalFrom(r)
|
||||
if p == nil {
|
||||
fail(w, 401, errors.New("Sign in to continue."))
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
out := []Org{}
|
||||
for _, o := range s.st.Orgs {
|
||||
switch {
|
||||
case p.serverKey() || p.User.ServerAdmin:
|
||||
out = append(out, *o)
|
||||
case p.Key != nil:
|
||||
if p.Key.OrgID == o.ID {
|
||||
out = append(out, *o)
|
||||
}
|
||||
case s.member(p.User.ID, o.ID) != nil:
|
||||
out = append(out, *o)
|
||||
}
|
||||
}
|
||||
writeJSON(w, 200, out)
|
||||
}
|
||||
|
||||
func (s *Server) deleteKey(w http.ResponseWriter, r *http.Request, c *orgCtx) {
|
||||
|
||||
Reference in new issue
Block a user