113 lines
3.2 KiB
Go
113 lines
3.2 KiB
Go
package httpapi
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"net/http"
|
||
|
|
|
||
|
|
"github.com/descrybe/descrybe-v2/apps/api/internal/auth"
|
||
|
|
"github.com/go-chi/chi/v5"
|
||
|
|
"github.com/google/uuid"
|
||
|
|
)
|
||
|
|
|
||
|
|
func (s *Server) handleListAPIKeys(w http.ResponseWriter, r *http.Request) {
|
||
|
|
cid, _ := CompanyIDFromContext(r.Context())
|
||
|
|
limit, offset := ParseLimitOffset(r)
|
||
|
|
const where = "company_id = $1 AND revoked_at IS NULL"
|
||
|
|
var total int64
|
||
|
|
if err := s.Pool.QueryRow(r.Context(), "SELECT count(*) FROM api_keys WHERE "+where, cid).Scan(&total); err != nil {
|
||
|
|
Error(w, http.StatusInternalServerError, "list failed")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
rows, err := s.Pool.Query(r.Context(), `
|
||
|
|
SELECT id, name, key_prefix, last_used_at, created_at
|
||
|
|
FROM api_keys WHERE `+where+`
|
||
|
|
ORDER BY created_at DESC LIMIT $2 OFFSET $3`, cid, limit, offset)
|
||
|
|
if err != nil {
|
||
|
|
Error(w, http.StatusInternalServerError, "list failed")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
defer rows.Close()
|
||
|
|
out := make([]map[string]any, 0)
|
||
|
|
for rows.Next() {
|
||
|
|
var id uuid.UUID
|
||
|
|
var name *string
|
||
|
|
var prefix string
|
||
|
|
var lastUsed, created any
|
||
|
|
if err := rows.Scan(&id, &name, &prefix, &lastUsed, &created); err != nil {
|
||
|
|
Error(w, http.StatusInternalServerError, "scan failed")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
out = append(out, map[string]any{
|
||
|
|
"id": id, "name": name, "key_prefix": prefix, "last_used_at": lastUsed, "created_at": created,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
JSON(w, http.StatusOK, map[string]any{"api_keys": out, "total": total, "limit": limit, "offset": offset})
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Server) handleCreateAPIKey(w http.ResponseWriter, r *http.Request) {
|
||
|
|
cid, _ := CompanyIDFromContext(r.Context())
|
||
|
|
uid, _ := UserIDFromContext(r.Context())
|
||
|
|
if !s.allowCompanyAdminOrPlatform(w, r) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if !s.requireFeatures(w, r, "settings.api_keys", "capability.api_access") {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
var body struct {
|
||
|
|
Name string `json:"name"`
|
||
|
|
}
|
||
|
|
if err := DecodeJSON(r, &body); err != nil {
|
||
|
|
Error(w, http.StatusBadRequest, "invalid json")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
raw, err := auth.RandomToken(24)
|
||
|
|
if err != nil {
|
||
|
|
Error(w, http.StatusInternalServerError, "key gen failed")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
full := "dk_" + raw
|
||
|
|
prefix := full[:10]
|
||
|
|
var id uuid.UUID
|
||
|
|
err = s.Pool.QueryRow(r.Context(), `
|
||
|
|
INSERT INTO api_keys (company_id, user_id, name, key_hash, key_prefix)
|
||
|
|
VALUES ($1, $2, $3, $4, $5) RETURNING id`,
|
||
|
|
cid, uid, nullIfEmpty(body.Name), auth.HashAPIKey(full), prefix).Scan(&id)
|
||
|
|
if err != nil {
|
||
|
|
Error(w, http.StatusInternalServerError, "create failed")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
JSON(w, http.StatusCreated, map[string]any{
|
||
|
|
"id": id, "name": body.Name, "key": full, "key_prefix": prefix,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Server) handleRevokeAPIKey(w http.ResponseWriter, r *http.Request) {
|
||
|
|
cid, _ := CompanyIDFromContext(r.Context())
|
||
|
|
if !s.allowCompanyAdminOrPlatform(w, r) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
id, err := uuid.Parse(chi.URLParam(r, "id"))
|
||
|
|
if err != nil {
|
||
|
|
Error(w, http.StatusBadRequest, "invalid id")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
tag, err := s.Pool.Exec(r.Context(), `
|
||
|
|
UPDATE api_keys SET revoked_at = now(), updated_at = now()
|
||
|
|
WHERE id = $1 AND company_id = $2 AND revoked_at IS NULL`, id, cid)
|
||
|
|
if err != nil {
|
||
|
|
Error(w, http.StatusInternalServerError, "revoke failed")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if tag.RowsAffected() == 0 {
|
||
|
|
Error(w, http.StatusNotFound, "not found")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
JSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
||
|
|
}
|
||
|
|
|
||
|
|
func nullIfEmpty(s string) *string {
|
||
|
|
if s == "" {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
return &s
|
||
|
|
}
|