pico
created pr with
57.1
added 57.2
1: b47b145 = 1: 461e887 feat(pgs): admins can impersonate
2: 7275c44 = 2: 91dabf9 chore: use `user_id` in log middleware to set the user ctx
cmds
checkout latest patchset:
ssh pr.pico.sh print 57 | git am -3checkout any patchset in a patch request:
ssh pr.pico.sh print 57.[rev] | git am -3add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add 57
Patchset
57.2
feat(pgs): admins can impersonate
Eric Bower
2025-03-28T15:53:44ZThis change will let admins impersonate any user for the pgs cli
Semantic diff summary
6 added,
31 modified,
0 signature changed,
6 removed
across 14 analyzed files
pkg/db/postgres/storage.go
-
method_declarationFindUserByNameadded -
method_declarationFindUserForNameremoved -
method_declarationFindFeatureadded -
method_declarationFindFeatureForUserremoved -
function_declarationFindFeatureForUsermodified -
function_declarationFindFeaturesForUsermodified -
method_declarationHasFeatureForUsermodified -
function_declarationInsertFeaturemodified -
function_declarationRemoveFeaturemodified -
method_declarationAddPicoPlusUsermodified
+3
-3
pkg/apps/auth/api.go
#
| ... | ... | @@ -303,7 +303,7 @@ func userHandler(apiConfig *shared.ApiConfig) http.HandlerFunc { | |
| 303 | 303 | "publicKey", data.PublicKey, | |
| 304 | 304 | ) | |
| 305 | 305 | ||
| 306 | - | user, err := apiConfig.Dbpool.FindUserForName(data.Username) | |
| 306 | + | user, err := apiConfig.Dbpool.FindUserByName(data.Username) | |
| 307 | 307 | if err != nil { | |
| 308 | 308 | apiConfig.Cfg.Logger.Error(err.Error()) | |
| 309 | 309 | http.Error(w, err.Error(), http.StatusNotFound) |
| ... | ... | @@ -461,7 +461,7 @@ func paymentWebhookHandler(apiConfig *shared.ApiConfig) http.HandlerFunc { | |
| 461 | 461 | status := event.Data.Attr.Status | |
| 462 | 462 | txID := fmt.Sprint(event.Data.Attr.OrderNumber) | |
| 463 | 463 | ||
| 464 | - | user, err := apiConfig.Dbpool.FindUserForName(username) | |
| 464 | + | user, err := apiConfig.Dbpool.FindUserByName(username) | |
| 465 | 465 | if err != nil { | |
| 466 | 466 | logger.Error("no user found with username", "username", username) | |
| 467 | 467 | w.WriteHeader(http.StatusOK) |
| ... | ... | @@ -624,7 +624,7 @@ func deserializeCaddyAccessLog(dbpool db.DB, access *AccessLog) (*db.AnalyticsVi | |
| 624 | 624 | } | |
| 625 | 625 | ||
| 626 | 626 | // get user ID | |
| 627 | - | user, err := dbpool.FindUserForName(props.Username) | |
| 627 | + | user, err := dbpool.FindUserByName(props.Username) | |
| 628 | 628 | if err != nil { | |
| 629 | 629 | return nil, fmt.Errorf("could not find user for name %s: %w", props.Username, err) | |
| 630 | 630 | } |
+2
-2
pkg/apps/auth/api_test.go
#
| ... | ... | @@ -220,7 +220,7 @@ func (a *AuthDb) AddPicoPlusUser(username, email, from, txid string) error { | |
| 220 | 220 | return nil | |
| 221 | 221 | } | |
| 222 | 222 | ||
| 223 | - | func (a *AuthDb) FindUserForName(username string) (*db.User, error) { | |
| 223 | + | func (a *AuthDb) FindUserByName(username string) (*db.User, error) { | |
| 224 | 224 | return &db.User{ID: testUserID, Name: username}, nil | |
| 225 | 225 | } | |
| 226 | 226 |
| ... | ... | @@ -243,7 +243,7 @@ func (a *AuthDb) FindKeysForUser(user *db.User) ([]*db.PublicKey, error) { | |
| 243 | 243 | return []*db.PublicKey{{ID: "1", UserID: user.ID, Name: "my-key", Key: "nice-pubkey", CreatedAt: &time.Time{}}}, nil | |
| 244 | 244 | } | |
| 245 | 245 | ||
| 246 | - | func (a *AuthDb) FindFeatureForUser(userID string, feature string) (*db.FeatureFlag, error) { | |
| 246 | + | func (a *AuthDb) FindFeature(userID string, feature string) (*db.FeatureFlag, error) { | |
| 247 | 247 | now := time.Date(2021, 8, 15, 14, 30, 45, 100, time.UTC) | |
| 248 | 248 | oneDayWarning := now.AddDate(0, 0, 1) | |
| 249 | 249 | return &db.FeatureFlag{ID: "2", UserID: userID, Name: "plus", ExpiresAt: &oneDayWarning, CreatedAt: &now}, nil |
+3
-3
pkg/apps/pastes/api.go
#
| ... | ... | @@ -78,7 +78,7 @@ func blogHandler(w http.ResponseWriter, r *http.Request) { | |
| 78 | 78 | logger := blogger.With("user", username) | |
| 79 | 79 | cfg := shared.GetCfg(r) | |
| 80 | 80 | ||
| 81 | - | user, err := dbpool.FindUserForName(username) | |
| 81 | + | user, err := dbpool.FindUserByName(username) | |
| 82 | 82 | if err != nil { | |
| 83 | 83 | logger.Info("user not found") | |
| 84 | 84 | http.Error(w, "user not found", http.StatusNotFound) |
| ... | ... | @@ -170,7 +170,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) { | |
| 170 | 170 | blogger := shared.GetLogger(r) | |
| 171 | 171 | logger := blogger.With("slug", slug, "user", username) | |
| 172 | 172 | ||
| 173 | - | user, err := dbpool.FindUserForName(username) | |
| 173 | + | user, err := dbpool.FindUserByName(username) | |
| 174 | 174 | if err != nil { | |
| 175 | 175 | logger.Info("paste not found") | |
| 176 | 176 | http.Error(w, "paste not found", http.StatusNotFound) |
| ... | ... | @@ -271,7 +271,7 @@ func postHandlerRaw(w http.ResponseWriter, r *http.Request) { | |
| 271 | 271 | blogger := shared.GetLogger(r) | |
| 272 | 272 | logger := blogger.With("user", username, "slug", slug) | |
| 273 | 273 | ||
| 274 | - | user, err := dbpool.FindUserForName(username) | |
| 274 | + | user, err := dbpool.FindUserByName(username) | |
| 275 | 275 | if err != nil { | |
| 276 | 276 | logger.Info("user not found") | |
| 277 | 277 | http.Error(w, "user not found", http.StatusNotFound) |
+4
-16
pkg/apps/pgs/cli_middleware.go
#
| ... | ... | @@ -10,26 +10,14 @@ import ( | |
| 10 | 10 | "github.com/picosh/pico/pkg/db" | |
| 11 | 11 | "github.com/picosh/pico/pkg/pssh" | |
| 12 | 12 | sendutils "github.com/picosh/pico/pkg/send/utils" | |
| 13 | - | "github.com/picosh/utils" | |
| 14 | 13 | ) | |
| 15 | 14 | ||
| 16 | 15 | func getUser(s *pssh.SSHServerConnSession, dbpool pgsdb.PgsDB) (*db.User, error) { | |
| 17 | - | if s.PublicKey() == nil { | |
| 18 | - | return nil, fmt.Errorf("key not found") | |
| 16 | + | userID, ok := s.Conn.Permissions.Extensions["user_id"] | |
| 17 | + | if !ok { | |
| 18 | + | return nil, fmt.Errorf("`user_id` extension not found") | |
| 19 | 19 | } | |
| 20 | - | ||
| 21 | - | key := utils.KeyForKeyText(s.PublicKey()) | |
| 22 | - | ||
| 23 | - | user, err := dbpool.FindUserByPubkey(key) | |
| 24 | - | if err != nil { | |
| 25 | - | return nil, err | |
| 26 | - | } | |
| 27 | - | ||
| 28 | - | if user.Name == "" { | |
| 29 | - | return nil, fmt.Errorf("must have username set") | |
| 30 | - | } | |
| 31 | - | ||
| 32 | - | return user, nil | |
| 20 | + | return dbpool.FindUser(userID) | |
| 33 | 21 | } | |
| 34 | 22 | ||
| 35 | 23 | type arrayFlags []string |
+2
-2
pkg/apps/pico/cli.go
#
| ... | ... | @@ -117,10 +117,10 @@ func Middleware(handler *CliHandler) pssh.SSHServerMiddleware { | |
| 117 | 117 | return err | |
| 118 | 118 | } | |
| 119 | 119 | ||
| 120 | - | ff, err := dbpool.FindFeatureForUser(user.ID, "plus") | |
| 120 | + | ff, err := dbpool.FindFeature(user.ID, "plus") | |
| 121 | 121 | if err != nil { | |
| 122 | 122 | handler.Logger.Error("Unable to find plus feature flag", "err", err, "user", user, "command", args) | |
| 123 | - | ff, err = dbpool.FindFeatureForUser(user.ID, "bouncer") | |
| 123 | + | ff, err = dbpool.FindFeature(user.ID, "bouncer") | |
| 124 | 124 | if err != nil { | |
| 125 | 125 | handler.Logger.Error("Unable to find bouncer feature flag", "err", err, "user", user, "command", args) | |
| 126 | 126 | sesh.Fatal(err) |
+6
-6
pkg/apps/prose/api.go
#
| ... | ... | @@ -125,7 +125,7 @@ func blogStyleHandler(w http.ResponseWriter, r *http.Request) { | |
| 125 | 125 | logger := shared.GetLogger(r) | |
| 126 | 126 | cfg := shared.GetCfg(r) | |
| 127 | 127 | ||
| 128 | - | user, err := dbpool.FindUserForName(username) | |
| 128 | + | user, err := dbpool.FindUserByName(username) | |
| 129 | 129 | if err != nil { | |
| 130 | 130 | logger.Info("blog not found", "user", username) | |
| 131 | 131 | http.Error(w, "blog not found", http.StatusNotFound) |
| ... | ... | @@ -155,7 +155,7 @@ func blogHandler(w http.ResponseWriter, r *http.Request) { | |
| 155 | 155 | logger := shared.GetLogger(r) | |
| 156 | 156 | cfg := shared.GetCfg(r) | |
| 157 | 157 | ||
| 158 | - | user, err := dbpool.FindUserForName(username) | |
| 158 | + | user, err := dbpool.FindUserByName(username) | |
| 159 | 159 | if err != nil { | |
| 160 | 160 | logger.Info("blog not found", "user", username) | |
| 161 | 161 | http.Error(w, "blog not found", http.StatusNotFound) |
| ... | ... | @@ -301,7 +301,7 @@ func postRawHandler(w http.ResponseWriter, r *http.Request) { | |
| 301 | 301 | logger := shared.GetLogger(r) | |
| 302 | 302 | logger = logger.With("slug", slug) | |
| 303 | 303 | ||
| 304 | - | user, err := dbpool.FindUserForName(username) | |
| 304 | + | user, err := dbpool.FindUserByName(username) | |
| 305 | 305 | if err != nil { | |
| 306 | 306 | logger.Info("blog not found", "user", username) | |
| 307 | 307 | http.Error(w, "blog not found", http.StatusNotFound) |
| ... | ... | @@ -341,7 +341,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) { | |
| 341 | 341 | dbpool := shared.GetDB(r) | |
| 342 | 342 | logger := shared.GetLogger(r) | |
| 343 | 343 | ||
| 344 | - | user, err := dbpool.FindUserForName(username) | |
| 344 | + | user, err := dbpool.FindUserByName(username) | |
| 345 | 345 | if err != nil { | |
| 346 | 346 | logger.Info("blog not found", "user", username) | |
| 347 | 347 | http.Error(w, "blog not found", http.StatusNotFound) |
| ... | ... | @@ -589,7 +589,7 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) { | |
| 589 | 589 | logger := shared.GetLogger(r) | |
| 590 | 590 | cfg := shared.GetCfg(r) | |
| 591 | 591 | ||
| 592 | - | user, err := dbpool.FindUserForName(username) | |
| 592 | + | user, err := dbpool.FindUserByName(username) | |
| 593 | 593 | if err != nil { | |
| 594 | 594 | logger.Info("rss feed not found", "user", username) | |
| 595 | 595 | http.Error(w, "rss feed not found", http.StatusNotFound) |
| ... | ... | @@ -852,7 +852,7 @@ func imgRequest(w http.ResponseWriter, r *http.Request) { | |
| 852 | 852 | logger := shared.GetLogger(r) | |
| 853 | 853 | dbpool := shared.GetDB(r) | |
| 854 | 854 | username := shared.GetUsernameFromRequest(r) | |
| 855 | - | user, err := dbpool.FindUserForName(username) | |
| 855 | + | user, err := dbpool.FindUserByName(username) | |
| 856 | 856 | if err != nil { | |
| 857 | 857 | logger.Error("could not find user", "username", username) | |
| 858 | 858 | http.Error(w, "could find user", http.StatusNotFound) |
+3
-3
pkg/db/db.go
#
| ... | ... | @@ -212,7 +212,7 @@ type Token struct { | |
| 212 | 212 | type FeatureFlag struct { | |
| 213 | 213 | ID string `json:"id" db:"id"` | |
| 214 | 214 | UserID string `json:"user_id" db:"user_id"` | |
| 215 | - | PaymentHistoryID string `json:"payment_history_id" db:"payment_history_id"` | |
| 215 | + | PaymentHistoryID sql.NullString `json:"payment_history_id" db:"payment_history_id"` | |
| 216 | 216 | Name string `json:"name" db:"name"` | |
| 217 | 217 | CreatedAt *time.Time `json:"created_at" db:"created_at"` | |
| 218 | 218 | ExpiresAt *time.Time `json:"expires_at" db:"expires_at"` |
| ... | ... | @@ -370,7 +370,7 @@ type DB interface { | |
| 370 | 370 | RemoveKeys(pubkeyIDs []string) error | |
| 371 | 371 | ||
| 372 | 372 | FindUsers() ([]*User, error) | |
| 373 | - | FindUserForName(name string) (*User, error) | |
| 373 | + | FindUserByName(name string) (*User, error) | |
| 374 | 374 | FindUserForNameAndKey(name string, pubkey string) (*User, error) | |
| 375 | 375 | FindUserForKey(name string, pubkey string) (*User, error) | |
| 376 | 376 | FindUserByPubkey(pubkey string) (*User, error) |
| ... | ... | @@ -414,7 +414,7 @@ type DB interface { | |
| 414 | 414 | FindVisitSiteList(opts *SummaryOpts) ([]*VisitUrl, error) | |
| 415 | 415 | ||
| 416 | 416 | AddPicoPlusUser(username, email, paymentType, txId string) error | |
| 417 | - | FindFeatureForUser(userID string, feature string) (*FeatureFlag, error) | |
| 417 | + | FindFeature(userID string, feature string) (*FeatureFlag, error) | |
| 418 | 418 | FindFeaturesForUser(userID string) ([]*FeatureFlag, error) | |
| 419 | 419 | HasFeatureForUser(userID string, feature string) bool | |
| 420 | 420 | FindTotalSizeForUser(userID string) (int, error) |
+9
-9
pkg/db/postgres/storage.go
#
| ... | ... | @@ -617,14 +617,14 @@ func (me *PsqlDB) ValidateName(name string) (bool, error) { | |
| 617 | 617 | if !v { | |
| 618 | 618 | return false, fmt.Errorf("%s is invalid: %w", lower, db.ErrNameInvalid) | |
| 619 | 619 | } | |
| 620 | - | user, _ := me.FindUserForName(lower) | |
| 620 | + | user, _ := me.FindUserByName(lower) | |
| 621 | 621 | if user == nil { | |
| 622 | 622 | return true, nil | |
| 623 | 623 | } | |
| 624 | 624 | return false, fmt.Errorf("%s already taken: %w", lower, db.ErrNameTaken) | |
| 625 | 625 | } | |
| 626 | 626 | ||
| 627 | - | func (me *PsqlDB) FindUserForName(name string) (*db.User, error) { | |
| 627 | + | func (me *PsqlDB) FindUserByName(name string) (*db.User, error) { | |
| 628 | 628 | user := &db.User{} | |
| 629 | 629 | r := me.Db.QueryRow(sqlSelectUserForName, strings.ToLower(name)) | |
| 630 | 630 | err := r.Scan(&user.ID, &user.Name, &user.CreatedAt) |
| ... | ... | @@ -1457,7 +1457,7 @@ func (me *PsqlDB) FindTagsForPost(postID string) ([]string, error) { | |
| 1457 | 1457 | return tags, nil | |
| 1458 | 1458 | } | |
| 1459 | 1459 | ||
| 1460 | - | func (me *PsqlDB) FindFeatureForUser(userID string, feature string) (*db.FeatureFlag, error) { | |
| 1460 | + | func (me *PsqlDB) FindFeature(userID string, feature string) (*db.FeatureFlag, error) { | |
| 1461 | 1461 | ff := &db.FeatureFlag{} | |
| 1462 | 1462 | // payment history is allowed to be null | |
| 1463 | 1463 | // https://devtidbits.com/2020/08/03/go-sql-error-converting-null-to-string-is-unsupported/ |
| ... | ... | @@ -1507,7 +1507,7 @@ func (me *PsqlDB) FindFeaturesForUser(userID string) ([]*db.FeatureFlag, error) | |
| 1507 | 1507 | if err != nil { | |
| 1508 | 1508 | return features, err | |
| 1509 | 1509 | } | |
| 1510 | - | ff.PaymentHistoryID = paymentHistoryID.String | |
| 1510 | + | ff.PaymentHistoryID = paymentHistoryID | |
| 1511 | 1511 | ||
| 1512 | 1512 | features = append(features, ff) | |
| 1513 | 1513 | } |
| ... | ... | @@ -1518,7 +1518,7 @@ func (me *PsqlDB) FindFeaturesForUser(userID string) ([]*db.FeatureFlag, error) | |
| 1518 | 1518 | } | |
| 1519 | 1519 | ||
| 1520 | 1520 | func (me *PsqlDB) HasFeatureForUser(userID string, feature string) bool { | |
| 1521 | - | ff, err := me.FindFeatureForUser(userID, feature) | |
| 1521 | + | ff, err := me.FindFeature(userID, feature) | |
| 1522 | 1522 | if err != nil { | |
| 1523 | 1523 | return false | |
| 1524 | 1524 | } |
| ... | ... | @@ -1695,7 +1695,7 @@ func (me *PsqlDB) InsertFeature(userID, name string, expiresAt time.Time) (*db.F | |
| 1695 | 1695 | return nil, err | |
| 1696 | 1696 | } | |
| 1697 | 1697 | ||
| 1698 | - | feature, err := me.FindFeatureForUser(userID, name) | |
| 1698 | + | feature, err := me.FindFeature(userID, name) | |
| 1699 | 1699 | if err != nil { | |
| 1700 | 1700 | return nil, err | |
| 1701 | 1701 | } |
| ... | ... | @@ -1709,7 +1709,7 @@ func (me *PsqlDB) RemoveFeature(userID string, name string) error { | |
| 1709 | 1709 | } | |
| 1710 | 1710 | ||
| 1711 | 1711 | func (me *PsqlDB) createFeatureExpiresAt(userID, name string) time.Time { | |
| 1712 | - | ff, _ := me.FindFeatureForUser(userID, name) | |
| 1712 | + | ff, _ := me.FindFeature(userID, name) | |
| 1713 | 1713 | if ff == nil { | |
| 1714 | 1714 | t := time.Now() | |
| 1715 | 1715 | return t.AddDate(1, 0, 0) |
| ... | ... | @@ -1718,7 +1718,7 @@ func (me *PsqlDB) createFeatureExpiresAt(userID, name string) time.Time { | |
| 1718 | 1718 | } | |
| 1719 | 1719 | ||
| 1720 | 1720 | func (me *PsqlDB) AddPicoPlusUser(username, email, paymentType, txId string) error { | |
| 1721 | - | user, err := me.FindUserForName(username) | |
| 1721 | + | user, err := me.FindUserByName(username) | |
| 1722 | 1722 | if err != nil { | |
| 1723 | 1723 | return err | |
| 1724 | 1724 | } |
+2
-2
pkg/db/stub/stub.go
#
| ... | ... | @@ -189,7 +189,7 @@ func (me *StubDB) FindTagsForPost(postID string) ([]string, error) { | |
| 189 | 189 | return []string{}, notImpl | |
| 190 | 190 | } | |
| 191 | 191 | ||
| 192 | - | func (me *StubDB) FindFeatureForUser(userID string, feature string) (*db.FeatureFlag, error) { | |
| 192 | + | func (me *StubDB) FindFeature(userID string, feature string) (*db.FeatureFlag, error) { | |
| 193 | 193 | return nil, notImpl | |
| 194 | 194 | } | |
| 195 | 195 |
+1
-1
pkg/tui/tuns.go
#
| ... | ... | @@ -230,7 +230,7 @@ func (m *TunsPage) HandleEvent(ev vaxis.Event, ph vxfw.EventPhase) (vxfw.Command | |
| 230 | 230 | switch msg := ev.(type) { | |
| 231 | 231 | case PageIn: | |
| 232 | 232 | m.loading = true | |
| 233 | - | ff, _ := m.shared.Dbpool.FindFeatureForUser(m.shared.User.ID, "admin") | |
| 233 | + | ff, _ := m.shared.Dbpool.FindFeature(m.shared.User.ID, "admin") | |
| 234 | 234 | if ff != nil { | |
| 235 | 235 | m.isAdmin = true | |
| 236 | 236 | } |
+2
-2
pkg/tui/ui.go
#
| ... | ... | @@ -296,7 +296,7 @@ func FindUser(shrd *SharedModel) (*db.User, error) { | |
| 296 | 296 | return nil, fmt.Errorf("only admins can impersonate a user") | |
| 297 | 297 | } | |
| 298 | 298 | impersonate := strings.Replace(usr, adminPrefix, "", 1) | |
| 299 | - | user, err = shrd.Dbpool.FindUserForName(impersonate) | |
| 299 | + | user, err = shrd.Dbpool.FindUserByName(impersonate) | |
| 300 | 300 | if err != nil { | |
| 301 | 301 | return nil, err | |
| 302 | 302 | } |
| ... | ... | @@ -311,7 +311,7 @@ func FindFeatureFlag(shrd *SharedModel, name string) (*db.FeatureFlag, error) { | |
| 311 | 311 | return nil, nil | |
| 312 | 312 | } | |
| 313 | 313 | ||
| 314 | - | ff, err := shrd.Dbpool.FindFeatureForUser(shrd.User.ID, name) | |
| 314 | + | ff, err := shrd.Dbpool.FindFeature(shrd.User.ID, name) | |
| 315 | 315 | if err != nil { | |
| 316 | 316 | return nil, err | |
| 317 | 317 | } |