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
2121 }
2222 }
2323
24+ if aclType == "private" {
25+ return false
26+ }
27+
2428 if aclType == "pico" {
2529 if requester == nil {
2630 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
147147 # => https://erock-mysite.pgs.sh/index.html
148148
149149 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.
150156 `
151157 helpStr += "\r\nCommands: [help, stats, ls, fzf, rm, link, unlink, prune, retain, depends, acl, cache]\r\n"
152158 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 {
236236 return err
237237 }
238238
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+
239245 err := opts.acl(projectName, *aclType, acls)
240246 opts.notice()
241247 opts.bail(err)
+9, -1
......@@ -1,6 +1,10 @@
11 package pgsdb
22
3-import "github.com/picosh/pico/pkg/db"
3+import (
4+ "strings"
5+
6+ "github.com/picosh/pico/pkg/db"
7+)
48
59 type PgsDB interface {
610 FindUser(userID string) (*db.User, error)
......@@ -27,3 +31,7 @@ type PgsDB interface {
2731
2832 Close() error
2933 }
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.
154154 )
155155 return nil, err
156156 }
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+ }
157163 return me.FindProjectByName(userID, projectName)
158164 }
159165
+1, -1
......@@ -82,7 +82,7 @@ type Project struct {
8282 }
8383
8484 type ProjectAcl struct {
85- Type string `json:"type" db:"type"`
85+ Type string `json:"type" db:"type"` // public, pico, pubkeys, private
8686 Data []string `json:"data" db:"data"`
8787 }
8888