repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

pico / pkg / shared
Eric Bower  ·  2025-03-28

ssh.go

 1package shared
 2
 3import (
 4	"fmt"
 5	"log/slog"
 6	"strings"
 7
 8	"github.com/picosh/pico/pkg/db"
 9	"github.com/picosh/utils"
10	"golang.org/x/crypto/ssh"
11)
12
13type SshAuthHandler struct {
14	DB     AuthFindUser
15	Logger *slog.Logger
16}
17
18type AuthFindUser interface {
19	FindUserByPubkey(key string) (*db.User, error)
20	FindUserByName(name string) (*db.User, error)
21	FindFeature(userID, name string) (*db.FeatureFlag, error)
22}
23
24func NewSshAuthHandler(dbh AuthFindUser, logger *slog.Logger) *SshAuthHandler {
25	return &SshAuthHandler{
26		DB:     dbh,
27		Logger: logger,
28	}
29}
30
31func (r *SshAuthHandler) PubkeyAuthHandler(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
32	pubkey := utils.KeyForKeyText(key)
33	user, err := r.DB.FindUserByPubkey(pubkey)
34	if err != nil {
35		r.Logger.Error(
36			"could not find user for key",
37			"keyType", key.Type(),
38			"key", string(key.Marshal()),
39			"err", err,
40		)
41		return nil, err
42	}
43
44	if user.Name == "" {
45		r.Logger.Error("username is not set")
46		return nil, fmt.Errorf("username is not set")
47	}
48
49	// impersonation
50	impID := user.ID
51	adminPrefix := "admin__"
52	usr := conn.User()
53	if strings.HasPrefix(usr, adminPrefix) {
54		ff, err := r.DB.FindFeature(user.ID, "admin")
55		if err != nil {
56			return nil, fmt.Errorf("only admins can impersonate a user: %w", err)
57		}
58		if !ff.IsValid() {
59			return nil, fmt.Errorf("expired admin feature flag, cannot impersonate a user")
60		}
61
62		impersonate := strings.Replace(usr, adminPrefix, "", 1)
63		user, err = r.DB.FindUserByName(impersonate)
64		if err != nil {
65			return nil, err
66		}
67	}
68
69	return &ssh.Permissions{
70		Extensions: map[string]string{
71			"imp_id":  impID,
72			"user_id": user.ID,
73			"pubkey":  pubkey,
74		},
75	}, nil
76}
77
78func FindPlusFF(dbpool db.DB, cfg *ConfigSite, userID string) *db.FeatureFlag {
79	ff, _ := dbpool.FindFeature(userID, "plus")
80	// we have free tiers so users might not have a feature flag
81	// in which case we set sane defaults
82	if ff == nil {
83		ff = db.NewFeatureFlag(
84			userID,
85			"plus",
86			cfg.MaxSize,
87			cfg.MaxAssetSize,
88			cfg.MaxSpecialFileSize,
89		)
90	}
91	// this is jank
92	ff.Data.StorageMax = ff.FindStorageMax(cfg.MaxSize)
93	ff.Data.FileMax = ff.FindFileMax(cfg.MaxAssetSize)
94	ff.Data.SpecialFileMax = ff.FindSpecialFileMax(cfg.MaxSpecialFileSize)
95	return ff
96}