Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
73 lines
2.3 KiB
Go
73 lines
2.3 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
|
|
"github.com/danielgtaylor/huma/v2"
|
|
|
|
"github.com/v10networks/nmon/internal/auth"
|
|
"github.com/v10networks/nmon/internal/store"
|
|
)
|
|
|
|
// apiTokenIDPrefix distinguishes the id of a viewer or admin token from a push token's: the two kinds live in
|
|
// different tables, so their numbers overlap.
|
|
const apiTokenIDPrefix = "a"
|
|
|
|
// APITokenAdmin mints, lists and revokes viewer and admin API tokens. *auth.Tokens implements it.
|
|
type APITokenAdmin interface {
|
|
Mint(ctx context.Context, actor string, o auth.MintOptions) (*store.APIToken, string, error)
|
|
Revoke(ctx context.Context, actor string, id int64) error
|
|
List(ctx context.Context) ([]store.APIToken, error)
|
|
}
|
|
|
|
func apiTokenView(t *store.APIToken) TokenView {
|
|
return TokenView{
|
|
ID: apiTokenIDPrefix + strconv.FormatInt(t.ID, 10), Name: t.Name, Scope: t.Scope, Devices: []string{},
|
|
ExpiresAt: utcp(t.ExpiresAt), RevokedAt: utcp(t.RevokedAt),
|
|
}
|
|
}
|
|
|
|
func (s *server) createAPIToken(ctx context.Context, name string, b TokenBody) (*struct{ Body TokenCreated }, error) {
|
|
if s.APITokens == nil {
|
|
return nil, errNotConfigured("API tokens")
|
|
}
|
|
if len(b.Devices) > 0 {
|
|
return nil, huma.Error422UnprocessableEntity("devices: only push tokens are bound to devices")
|
|
}
|
|
row, secret, err := s.APITokens.Mint(ctx, actorOf(ctx), auth.MintOptions{Name: name, Scope: auth.Role(b.Scope), ExpiresAt: b.ExpiresAt})
|
|
if err != nil {
|
|
return nil, s.storeErr(err) // Mint audits the creation itself
|
|
}
|
|
return &struct{ Body TokenCreated }{TokenCreated{TokenView: apiTokenView(row), Secret: secret}}, nil
|
|
}
|
|
|
|
func (s *server) listAPITokens(ctx context.Context) ([]TokenView, error) {
|
|
if s.APITokens == nil {
|
|
return nil, nil
|
|
}
|
|
rows, err := s.APITokens.List(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]TokenView, 0, len(rows))
|
|
for i := range rows {
|
|
out = append(out, apiTokenView(&rows[i])) // the hash stays in the row
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s *server) revokeAPIToken(ctx context.Context, rest string) (*struct{}, error) {
|
|
if s.APITokens == nil {
|
|
return nil, errNotConfigured("API tokens")
|
|
}
|
|
id, err := idParam(rest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.APITokens.Revoke(ctx, actorOf(ctx), id); err != nil {
|
|
return nil, s.storeErr(err) // Revoke audits itself
|
|
}
|
|
return &struct{}{}, nil
|
|
}
|