1package admin
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "strings"
8 "time"
9
10 "tug/internal/platform/sshkey/openssh"
11 "tug/internal/storage"
12)
13
14type SSHKey struct {
15 ID string
16 Fingerprint string
17 Type string
18 Label string
19 AddedAt time.Time
20}
21
22func (s *Service) sshKeys(ctx context.Context, id string) ([]SSHKey, error) {
23 keys, err := s.store.SSHKeysOf(ctx, id)
24 if err != nil {
25 return nil, failed("read user's ssh keys", err)
26 }
27 out := make([]SSHKey, 0, len(keys))
28 for _, k := range keys {
29 out = append(out, sshKeyOf(k))
30 }
31 return out, nil
32}
33
34func (s *Service) AddSSHKey(ctx context.Context, actor, id, line, label string) (SSHKey, error) {
35 const operation = "add ssh key"
36 key, err := openssh.Parse(line)
37 if err != nil {
38 return SSHKey{}, failed(operation, err)
39 }
40 if err := storage.CheckSSHKeyLabel(strings.TrimSpace(label)); err != nil {
41 return SSHKey{}, failed(operation, err)
42 }
43 added, err := s.store.AddSSHKeyAudited(ctx, storage.AddSSHKeyInput{UserID: id, Key: key, Label: label}, actor)
44 switch {
45 case errors.Is(err, storage.ErrSSHKeyDisabled):
46 return SSHKey{}, ProblemSSHKeyDisabled
47 case errors.Is(err, storage.ErrSSHKeyPending):
48 return SSHKey{}, ProblemSSHKeyPending
49 case err != nil:
50 return SSHKey{}, target(operation, err)
51 }
52 return sshKeyOf(added), nil
53}
54
55func (s *Service) RemoveSSHKey(ctx context.Context, actor, id, keyID string) (SSHKey, error) {
56 removed, err := s.store.RemoveSSHKeyAudited(ctx, id, keyID, "", actor)
57 switch {
58 case errors.Is(err, storage.ErrSSHKeyNotFound):
59 return SSHKey{}, fmt.Errorf("admin: remove ssh key: %w: %w", ErrNotFound, err)
60 case err != nil:
61 return SSHKey{}, target("remove ssh key", err)
62 }
63 return sshKeyOf(removed), nil
64}
65
66func sshKeyOf(k storage.SSHKey) SSHKey {
67 return SSHKey{ID: k.ID, Fingerprint: k.Fingerprint, Type: k.Type, Label: k.Label, AddedAt: k.AddedAt}
68}