Add token management helpers for API credentials

This commit is contained in:
Joe Julian
2026-03-29 23:17:34 -07:00
parent 74dfe3f3d0
commit e9eca73336
4 changed files with 270 additions and 0 deletions
+35
View File
@@ -34,8 +34,11 @@ var (
ErrInvalidToken = errors.New("invalid api token")
ErrExpiredToken = errors.New("expired api token")
ErrDisabledToken = errors.New("disabled api token")
ErrTokenNotFound = errors.New("api token not found")
)
var EntryPath = []string{"Root", "API Tokens"}
type Effect string
type Operation string
type ResourceKind string
@@ -192,6 +195,38 @@ func Entries(model vault.Model) ([]Token, error) {
return out, nil
}
func Find(model vault.Model, id string) (Token, error) {
tokens, err := Entries(model)
if err != nil {
return Token{}, err
}
for _, token := range tokens {
if token.ID == id {
return token, nil
}
}
return Token{}, ErrTokenNotFound
}
func Upsert(model *vault.Model, token Token) {
model.UpsertEntry(token.Entry(EntryPath))
model.CreateGroup([]string{"Root"}, "API Tokens")
}
func Delete(model *vault.Model, id string) error {
for i, entry := range model.Entries {
token, ok, err := TokenFromEntry(entry)
if err != nil {
return err
}
if ok && token.ID == id {
model.Entries = append(model.Entries[:i], model.Entries[i+1:]...)
return nil
}
}
return ErrTokenNotFound
}
func TokenFromEntry(entry vault.Entry) (Token, bool, error) {
if entry.Fields[FieldType] != EntryTypeAPIToken {
return Token{}, false, nil
+54
View File
@@ -72,6 +72,60 @@ func TestEntriesFiltersOnlyAPITokens(t *testing.T) {
}
}
func TestUpsertCreatesAndUpdatesVaultTokenEntry(t *testing.T) {
t.Parallel()
model := vault.Model{}
token := Token{
ID: "token-1",
Name: "CLI",
ClientName: "grpc-cli",
SecretHash: "hash-1",
CreatedAt: time.Date(2026, 3, 29, 12, 0, 0, 0, time.UTC),
}
Upsert(&model, token)
if len(model.Entries) != 1 {
t.Fatalf("len(model.Entries) = %d, want 1", len(model.Entries))
}
if got := model.ChildGroups([]string{"Root"}); !slices.Equal(got, []string{"API Tokens"}) {
t.Fatalf("ChildGroups(Root) = %v, want [API Tokens]", got)
}
token.ClientName = "rotated-client"
token.Disabled = true
Upsert(&model, token)
got, err := Find(model, "token-1")
if err != nil {
t.Fatalf("Find() error = %v", err)
}
if got.ClientName != "rotated-client" || !got.Disabled {
t.Fatalf("Find() = %#v, want updated client name and disabled state", got)
}
}
func TestDeleteRemovesTokenEntryByTokenID(t *testing.T) {
t.Parallel()
model := vault.Model{
Entries: []vault.Entry{
Token{ID: "token-1", Name: "CLI", SecretHash: "hash-1", CreatedAt: time.Date(2026, 3, 29, 12, 0, 0, 0, time.UTC)}.Entry(EntryPath),
{ID: "entry-1", Title: "Regular Entry"},
},
}
if err := Delete(&model, "token-1"); err != nil {
t.Fatalf("Delete() error = %v", err)
}
if len(model.Entries) != 1 || model.Entries[0].ID != "entry-1" {
t.Fatalf("model.Entries after Delete = %#v, want only regular entry", model.Entries)
}
if err := Delete(&model, "missing"); err != ErrTokenNotFound {
t.Fatalf("Delete(missing) error = %v, want %v", err, ErrTokenNotFound)
}
}
func TestIssueRotateDisableAndRevokeToken(t *testing.T) {
t.Parallel()