Commit a83183c
Eric Bower
·
2026-03-02 09:38:24 -0500 EST
parent 970de49
feat(pgs): project names prefixed with `private-` will have new acl type "private" For security reasons once a project is named `private-` it can never have its ACL type changed. Right now we do not support making other projects private.
7 files changed,
+79,
-2
+4,
-0
| ... | ... | @@ -21,6 +21,10 @@ func HasProjectAccess(project *db.Project, owner *db.User, requester *db.User, p | |
| 21 | 21 | } | |
| 22 | 22 | } | |
| 23 | 23 | ||
| 24 | + | if aclType == "private" { | |
| 25 | + | return false | |
| 26 | + | } | |
| 27 | + | ||
| 24 | 28 | if aclType == "pico" { | |
| 25 | 29 | if requester == nil { | |
| 26 | 30 | return false |
+47,
-0
| ... | ... | @@ -0,0 +1,47 @@ | |
| 1 | + | package pgs | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "log/slog" | |
| 5 | + | "net/http" | |
| 6 | + | "net/http/httptest" | |
| 7 | + | "strings" | |
| 8 | + | "testing" | |
| 9 | + | ||
| 10 | + | "github.com/picosh/pico/pkg/shared" | |
| 11 | + | "github.com/picosh/pico/pkg/shared/storage" | |
| 12 | + | ) | |
| 13 | + | ||
| 14 | + | func TestPrivateProjectDeniesWebAccess(t *testing.T) { | |
| 15 | + | logger := slog.Default() | |
| 16 | + | dbpool := NewPgsDb(logger) | |
| 17 | + | bucketName := shared.GetAssetBucketName(dbpool.Users[0].ID) | |
| 18 | + | ||
| 19 | + | // Mark the test project as private | |
| 20 | + | project, err := dbpool.FindProjectByName(dbpool.Users[0].ID, "test") | |
| 21 | + | if err != nil { | |
| 22 | + | t.Fatalf("failed to get project: %v", err) | |
| 23 | + | } | |
| 24 | + | project.Acl.Type = "private" | |
| 25 | + | project.Acl.Data = []string{} | |
| 26 | + | ||
| 27 | + | request := httptest.NewRequest("GET", "https://"+dbpool.Users[0].Name+"-test.pgs.test/", strings.NewReader("")) | |
| 28 | + | responseRecorder := httptest.NewRecorder() | |
| 29 | + | ||
| 30 | + | st, _ := storage.NewStorageMemory(map[string]map[string]string{ | |
| 31 | + | bucketName: { | |
| 32 | + | "/test/index.html": "hello world!", | |
| 33 | + | }, | |
| 34 | + | }) | |
| 35 | + | pubsub := NewPubsubChan() | |
| 36 | + | defer func() { | |
| 37 | + | _ = pubsub.Close() | |
| 38 | + | }() | |
| 39 | + | cfg := NewPgsConfig(logger, dbpool, st, pubsub) | |
| 40 | + | cfg.Domain = "pgs.test" | |
| 41 | + | router := NewWebRouter(cfg) | |
| 42 | + | router.ServeHTTP(responseRecorder, request) | |
| 43 | + | ||
| 44 | + | if responseRecorder.Code != http.StatusUnauthorized { | |
| 45 | + | t.Errorf("want status %d, got %d", http.StatusUnauthorized, responseRecorder.Code) | |
| 46 | + | } | |
| 47 | + | } |
+6,
-0
| ... | ... | @@ -147,6 +147,12 @@ You can also use unix pipes to directly upload files by providing the project na | |
| 147 | 147 | # => https://erock-mysite.pgs.sh/index.html | |
| 148 | 148 | ||
| 149 | 149 | The leading "/" is important. | |
| 150 | + | ||
| 151 | + | You can also create private projects when you prefix the project name with 'private': | |
| 152 | + | ||
| 153 | + | rsync -rv ./public/ pgs.sh:/private-site/ | |
| 154 | + | ||
| 155 | + | This means only you can access the site through a web tunnel or by downloading the files. | |
| 150 | 156 | ` | |
| 151 | 157 | helpStr += "\r\nCommands: [help, stats, ls, fzf, rm, link, unlink, prune, retain, depends, acl, cache]\r\n" | |
| 152 | 158 | helpStr += "For most of these commands you can provide a `-h` to learn about its usage.\r\n" |
+6,
-0
| ... | ... | @@ -236,6 +236,12 @@ func Middleware(handler *UploadAssetHandler) pssh.SSHServerMiddleware { | |
| 236 | 236 | return err | |
| 237 | 237 | } | |
| 238 | 238 | ||
| 239 | + | if pgsdb.IsProjectPrivate(projectName) { | |
| 240 | + | err = fmt.Errorf("projects prefixed with `private-` can *never* have their access changed; however you can symlink to it") | |
| 241 | + | opts.bail(err) | |
| 242 | + | return err | |
| 243 | + | } | |
| 244 | + | ||
| 239 | 245 | err := opts.acl(projectName, *aclType, acls) | |
| 240 | 246 | opts.notice() | |
| 241 | 247 | opts.bail(err) |
+9,
-1
| ... | ... | @@ -1,6 +1,10 @@ | |
| 1 | 1 | package pgsdb | |
| 2 | 2 | ||
| 3 | - | import "github.com/picosh/pico/pkg/db" | |
| 3 | + | import ( | |
| 4 | + | "strings" | |
| 5 | + | ||
| 6 | + | "github.com/picosh/pico/pkg/db" | |
| 7 | + | ) | |
| 4 | 8 | ||
| 5 | 9 | type PgsDB interface { | |
| 6 | 10 | FindUser(userID string) (*db.User, error) |
| ... | ... | @@ -27,3 +31,7 @@ type PgsDB interface { | |
| 27 | 31 | ||
| 28 | 32 | Close() error | |
| 29 | 33 | } | |
| 34 | + | ||
| 35 | + | func IsProjectPrivate(projectName string) bool { | |
| 36 | + | return strings.HasPrefix(projectName, "private-") | |
| 37 | + | } |
+6,
-0
| ... | ... | @@ -154,6 +154,12 @@ func (me *PgsPsqlDB) UpsertProject(userID, projectName, projectDir string) (*db. | |
| 154 | 154 | ) | |
| 155 | 155 | return nil, err | |
| 156 | 156 | } | |
| 157 | + | if IsProjectPrivate(projectName) { | |
| 158 | + | err = me.UpdateProjectAcl(userID, projectName, db.ProjectAcl{Type: "private", Data: []string{}}) | |
| 159 | + | if err != nil { | |
| 160 | + | return nil, err | |
| 161 | + | } | |
| 162 | + | } | |
| 157 | 163 | return me.FindProjectByName(userID, projectName) | |
| 158 | 164 | } | |
| 159 | 165 |
+1,
-1