From eb05c13dde1081d6228214869c25fe57eaa4e95a Mon Sep 17 00:00:00 2001 From: Abhishek Shroff Date: Thu, 24 Oct 2024 21:35:36 +0530 Subject: [PATCH] [server] Add basic structure for public shares --- .../db/migrations/data/007_public_shares.sql | 14 +++++ server/internal/core/db/models.go | 11 ++++ server/internal/core/db/public.sql.go | 39 +++++++++++++ server/internal/core/fs/public.go | 5 ++ server/internal/core/fs/public/filesystem.go | 41 +++++++++++++ server/internal/core/fs/public/public.go | 58 +++++++++++++++++++ server/internal/core/fs/public/resource.go | 18 ++++++ server/sql/queries/public.sql | 5 ++ 8 files changed, 191 insertions(+) create mode 100644 server/internal/core/db/migrations/data/007_public_shares.sql create mode 100644 server/internal/core/db/public.sql.go create mode 100644 server/internal/core/fs/public.go create mode 100644 server/internal/core/fs/public/filesystem.go create mode 100644 server/internal/core/fs/public/public.go create mode 100644 server/internal/core/fs/public/resource.go create mode 100644 server/sql/queries/public.sql diff --git a/server/internal/core/db/migrations/data/007_public_shares.sql b/server/internal/core/db/migrations/data/007_public_shares.sql new file mode 100644 index 00000000..427b8b49 --- /dev/null +++ b/server/internal/core/db/migrations/data/007_public_shares.sql @@ -0,0 +1,14 @@ +CREATE TABLE public_shares( + id TEXT PRIMARY KEY, + created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted TIMESTAMP, + created_by TEXT NOT NULL REFERENCES users(username) ON UPDATE CASCADE ON DELETE CASCADE, + root UUID NOT NULL REFERENCES resources(id) ON UPDATE CASCADE ON DELETE CASCADE, + password_hash TEXT, + expires TIMESTAMP, + accesses_left INT +); + +---- create above / drop below ---- + +DROP TABLE public_shares; \ No newline at end of file diff --git a/server/internal/core/db/models.go b/server/internal/core/db/models.go index 0b61ed54..d6ee7728 100644 --- a/server/internal/core/db/models.go +++ b/server/internal/core/db/models.go @@ -16,6 +16,17 @@ type AccessToken struct { Username string } +type PublicShare struct { + ID string + Created pgtype.Timestamp + Deleted pgtype.Timestamp + CreatedBy string + Root uuid.UUID + PasswordHash pgtype.Text + Expires pgtype.Timestamp + AccessesLeft pgtype.Int4 +} + type Resource struct { ID uuid.UUID Permissions []byte diff --git a/server/internal/core/db/public.sql.go b/server/internal/core/db/public.sql.go new file mode 100644 index 00000000..0eb8cddc --- /dev/null +++ b/server/internal/core/db/public.sql.go @@ -0,0 +1,39 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.26.0 +// source: public.sql + +package db + +import ( + "context" +) + +const accessPublicShare = `-- name: AccessPublicShare :exec +UPDATE public_shares SET accesses_left = accesses_left - 1 WHERE id = $1 +` + +func (q *Queries) AccessPublicShare(ctx context.Context, id string) error { + _, err := q.db.Exec(ctx, accessPublicShare, id) + return err +} + +const publicShare = `-- name: PublicShare :one +SELECT id, created, deleted, created_by, root, password_hash, expires, accesses_left from public_shares WHERE id = $1 +` + +func (q *Queries) PublicShare(ctx context.Context, id string) (PublicShare, error) { + row := q.db.QueryRow(ctx, publicShare, id) + var i PublicShare + err := row.Scan( + &i.ID, + &i.Created, + &i.Deleted, + &i.CreatedBy, + &i.Root, + &i.PasswordHash, + &i.Expires, + &i.AccessesLeft, + ) + return i, err +} diff --git a/server/internal/core/fs/public.go b/server/internal/core/fs/public.go new file mode 100644 index 00000000..fa434c28 --- /dev/null +++ b/server/internal/core/fs/public.go @@ -0,0 +1,5 @@ +package fs + +func (f filesystem) CreatePublicShare(r Resource) { + +} diff --git a/server/internal/core/fs/public/filesystem.go b/server/internal/core/fs/public/filesystem.go new file mode 100644 index 00000000..103cb807 --- /dev/null +++ b/server/internal/core/fs/public/filesystem.go @@ -0,0 +1,41 @@ +package public + +import ( + "context" + + "github.com/google/uuid" + "github.com/jackc/pgx/v5" + "github.com/shroff/phylum/server/internal/core/db" + "github.com/shroff/phylum/server/internal/core/fs" + "github.com/shroff/phylum/server/internal/core/storage" +) + +type FileSystem interface { +} + +type filesystem struct { + ctx context.Context + db *db.DbHandler + cs storage.Storage + rootID uuid.UUID +} + +func (f filesystem) ResourceByPath(path string) (Resource, error) { + r, err := f.db.ResourceByPath(f.ctx, db.ResourceByPathParams{Root: f.rootID, Path: path}) + if err == pgx.ErrNoRows || !r.Found { + err = fs.ErrResourceNotFound + } + if err != nil { + return Resource{}, err + } + + return Resource{ + ID: r.ID, + Name: r.Name, + Dir: r.Dir, + Created: r.Created.Time, + Modified: r.Modified.Time, + ContentSize: r.ContentSize, + ContentSHA256: r.ContentSha256, + }, nil +} diff --git a/server/internal/core/fs/public/public.go b/server/internal/core/fs/public/public.go new file mode 100644 index 00000000..b67b3030 --- /dev/null +++ b/server/internal/core/fs/public/public.go @@ -0,0 +1,58 @@ +package public + +import ( + "context" + "net/http" + "time" + + "github.com/jackc/pgx/v5" + "github.com/shroff/phylum/server/internal/core/db" + "github.com/shroff/phylum/server/internal/core/errors" + "github.com/shroff/phylum/server/internal/core/storage" + "github.com/shroff/phylum/server/internal/core/util/crypt" +) + +var ErrPublicShareNotFound = errors.NewError(http.StatusUnauthorized, "public_share_not_found", "public share not found") + + +func OpenPublic(ctx context.Context, id string, password string) (FileSystem, error) { + // Check exists + s, err := db.Get().PublicShare(ctx, id) + if errors.Is(err, pgx.ErrNoRows) { + err = ErrPublicShareNotFound + } + if err != nil { + return nil, err + } + + // check password + if s.PasswordHash.Valid { + if ok, err := crypt.VerifyPassword(password, s.PasswordHash.String); err != nil { + return nil, err + } else if !ok { + return nil, ErrPublicShareNotFound + } + } + + // check expiration + if s.Expires.Valid { + if s.Expires.Time.Before(time.Now()) { + return nil, ErrPublicShareNotFound + } + } + + // check max accesses + if s.AccessesLeft.Valid { + if s.AccessesLeft.Int32 <= 0 { + return nil, ErrPublicShareNotFound + } + db.Get().AccessPublicShare(ctx, id) + } + + return filesystem{ + ctx: ctx, + db: db.Get(), + cs: storage.Get(), + rootID: s.Root, + }, nil +} diff --git a/server/internal/core/fs/public/resource.go b/server/internal/core/fs/public/resource.go new file mode 100644 index 00000000..4762c3c7 --- /dev/null +++ b/server/internal/core/fs/public/resource.go @@ -0,0 +1,18 @@ +package public + +import ( + "time" + + "github.com/google/uuid" +) + +type Resource struct { + ID uuid.UUID + Name string + Dir bool + Created time.Time + Modified time.Time + Deleted *time.Time + ContentSize int64 + ContentSHA256 string +} diff --git a/server/sql/queries/public.sql b/server/sql/queries/public.sql new file mode 100644 index 00000000..f749f146 --- /dev/null +++ b/server/sql/queries/public.sql @@ -0,0 +1,5 @@ +-- name: PublicShare :one +SELECT * from public_shares WHERE id = $1; + +-- name: AccessPublicShare :exec +UPDATE public_shares SET accesses_left = accesses_left - 1 WHERE id = $1;