mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-01-05 19:21:23 -06:00
[server] UserID+email instead of username
This commit is contained in:
@@ -57,8 +57,8 @@ func extractUserDetails(c *gin.Context) (user.User, error) {
|
||||
}
|
||||
return user.User{}, errAuthRequired
|
||||
} else if auth, ok := checkAuthHeader(header, "basic"); ok {
|
||||
if username, password, ok := decodeBasicAuth(auth); ok {
|
||||
if u, err := userManager.VerifyUserPassword(username, password); err == nil {
|
||||
if email, password, ok := decodeBasicAuth(auth); ok {
|
||||
if u, err := userManager.VerifyUserPassword(email, password); err == nil {
|
||||
return u, nil
|
||||
} else {
|
||||
return user.User{}, err
|
||||
@@ -82,15 +82,15 @@ func checkAuthHeader(header, prefix string) (string, bool) {
|
||||
return header[len(prefix):], true
|
||||
}
|
||||
|
||||
func decodeBasicAuth(auth string) (username, password string, ok bool) {
|
||||
func decodeBasicAuth(auth string) (email, password string, ok bool) {
|
||||
c, err := base64.StdEncoding.DecodeString(auth)
|
||||
if err != nil {
|
||||
return "", "", false
|
||||
}
|
||||
cs := string(c)
|
||||
username, password, ok = strings.Cut(cs, ":")
|
||||
email, password, ok = strings.Cut(cs, ":")
|
||||
if !ok {
|
||||
return "", "", false
|
||||
}
|
||||
return username, password, true
|
||||
return email, password, true
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
type passwordParams struct {
|
||||
Username string `json:"username" form:"username" binding:"required"`
|
||||
Email string `json:"email" form:"email" binding:"required"`
|
||||
Password string `json:"password" form:"password" binding:"required"`
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ type loginResponse struct {
|
||||
User loggedInUserResponse `json:"user"`
|
||||
}
|
||||
type loggedInUserResponse struct {
|
||||
Username string `json:"username"`
|
||||
Display string `json:"display"`
|
||||
Home pgtype.UUID `json:"home"`
|
||||
Email string `json:"email"`
|
||||
Display string `json:"display"`
|
||||
Home pgtype.UUID `json:"home"`
|
||||
}
|
||||
|
||||
func SetupRoutes(r *gin.RouterGroup) {
|
||||
@@ -35,22 +35,22 @@ func handlePasswordAuth(c *gin.Context) {
|
||||
var params passwordParams
|
||||
err := c.ShouldBind(¶ms)
|
||||
if err != nil {
|
||||
panic(errors.NewError(http.StatusBadRequest, "missing_params", "Username or password not specified"))
|
||||
panic(errors.NewError(http.StatusBadRequest, "missing_params", "Email or password not specified"))
|
||||
}
|
||||
|
||||
userManager := user.ManagerFromContext(c.Request.Context())
|
||||
if user, err := userManager.VerifyUserPassword(params.Username, params.Password); err != nil {
|
||||
if user, err := userManager.VerifyUserPassword(params.Email, params.Password); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
if token, err := userManager.CreateAccessToken(user.Username); err != nil {
|
||||
if token, err := userManager.CreateAccessToken(user.Email); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
c.JSON(200, loginResponse{
|
||||
AccessToken: token,
|
||||
User: loggedInUserResponse{
|
||||
Username: user.Username,
|
||||
Display: user.DisplayName,
|
||||
Home: user.Home,
|
||||
Email: user.Email,
|
||||
Display: user.DisplayName,
|
||||
Home: user.Home,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@ import (
|
||||
"github.com/shroff/phylum/server/internal/api/authenticator"
|
||||
"github.com/shroff/phylum/server/internal/api/v1/responses"
|
||||
"github.com/shroff/phylum/server/internal/core/fs"
|
||||
"github.com/shroff/phylum/server/internal/core/user"
|
||||
)
|
||||
|
||||
type shareParams struct {
|
||||
Path string `json:"path" form:"path" binding:"required"`
|
||||
Username string `json:"username" form:"username" binding:"required"`
|
||||
Email string `json:"email" form:"email" binding:"required"`
|
||||
Permissions fs.Permission `json:"permission" form:"permissions" binding:"required"`
|
||||
}
|
||||
|
||||
@@ -29,7 +30,12 @@ func handleShareRequest(c *gin.Context) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
r, err = r.UpdatePermissions(params.Username, params.Permissions)
|
||||
user, err := user.ManagerFromContext(c).UserByEmail(params.Email)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
r, err = r.UpdatePermissions(user.ID, params.Permissions)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ func handleHomeRoute(c *gin.Context) {
|
||||
|
||||
c.JSON(200, homeResponse{
|
||||
User: responses.User{
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
DisplayName: user.DisplayName,
|
||||
},
|
||||
Home: responses.FullResourceFromFS(r),
|
||||
|
||||
@@ -37,7 +37,7 @@ type ResourceFull struct {
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
DisplayName string `json:"display"`
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ func handleUsersListRoute(c *gin.Context) {
|
||||
result := make([]responses.User, len(users))
|
||||
for i, u := range users {
|
||||
result[i] = responses.User{
|
||||
Username: u.Username,
|
||||
Email: u.Email,
|
||||
DisplayName: u.DisplayName,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,10 @@ func SetupHandler(r *gin.RouterGroup) {
|
||||
func (h *handler) HandleRequest(c *gin.Context) {
|
||||
authSuccess := false
|
||||
var f fs.FileSystem
|
||||
if username, pass, ok := c.Request.BasicAuth(); ok {
|
||||
if email, pass, ok := c.Request.BasicAuth(); ok {
|
||||
ctx := c.Request.Context()
|
||||
userManager := user.ManagerFromContext(ctx)
|
||||
if u, err := userManager.VerifyUserPassword(username, pass); err == nil {
|
||||
if u, err := userManager.VerifyUserPassword(email, pass); err == nil {
|
||||
authSuccess = true
|
||||
root := c.Param("root")
|
||||
f = u.OpenFileSystem(ctx)
|
||||
|
||||
@@ -25,7 +25,7 @@ func User(cmd *cobra.Command) *user.User {
|
||||
} else if value == "" {
|
||||
u = nil
|
||||
} else {
|
||||
if user, err := user.ManagerFromContext(context.Background()).UserByUsername(value); err != nil {
|
||||
if user, err := user.ManagerFromContext(context.Background()).UserByEmail(value); err != nil {
|
||||
fmt.Println("could not find user '" + value + "': " + err.Error())
|
||||
os.Exit(1)
|
||||
} else {
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
func setupSetfaclCommand() *cobra.Command {
|
||||
cmd := cobra.Command{
|
||||
Use: "setfacl <path> <user> (none|read|write|share)",
|
||||
Use: "setfacl <path> <user-email> (none|read|write|share)",
|
||||
Short: "Access Control",
|
||||
Args: cobra.ExactArgs(3),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
@@ -25,23 +25,23 @@ func setupSetfaclCommand() *cobra.Command {
|
||||
fmt.Println("cannot update permissions for '" + path + "': " + err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
username := args[1]
|
||||
if _, err := user.ManagerFromContext(context.Background()).UserByUsername(username); err != nil {
|
||||
fmt.Println("cannot update permissions for user '" + username + "': " + err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
permission, err := parsePermissionString(args[2])
|
||||
if err != nil {
|
||||
fmt.Println("cannot update permissions for '" + path + "': " + err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if _, err := r.UpdatePermissions(username, permission); err != nil {
|
||||
fmt.Println("cannot update permissions for '" + path + "': " + err.Error())
|
||||
email := args[1]
|
||||
if user, err := user.ManagerFromContext(context.Background()).UserByEmail(email); err != nil {
|
||||
fmt.Println("cannot update permissions for user '" + email + "': " + err.Error())
|
||||
os.Exit(1)
|
||||
} else {
|
||||
if _, err := r.UpdatePermissions(user.ID, permission); err != nil {
|
||||
fmt.Println("cannot update permissions for '" + path + "': " + err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
return &cmd
|
||||
|
||||
@@ -19,18 +19,18 @@ import (
|
||||
|
||||
func setupUserAddCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "add username",
|
||||
Use: "add email",
|
||||
Short: "Add User",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
username := args[0]
|
||||
email := args[0]
|
||||
|
||||
displayName := username
|
||||
displayName := email
|
||||
if n, err := cmd.Flags().GetString("name"); err != nil {
|
||||
fmt.Println("invalid value for flag 'name': " + err.Error())
|
||||
os.Exit(1)
|
||||
} else if n != "" {
|
||||
displayName = username
|
||||
displayName = email
|
||||
}
|
||||
|
||||
password, err := cmd.Flags().GetString("password")
|
||||
@@ -67,7 +67,7 @@ func setupUserAddCommand() *cobra.Command {
|
||||
homePath := ""
|
||||
if !noCreateHome {
|
||||
basePath, _ := cmd.Flags().GetString("base-dir")
|
||||
homePath = strings.TrimRight(basePath, "/") + "/" + username
|
||||
homePath = strings.TrimRight(basePath, "/") + "/" + email
|
||||
}
|
||||
|
||||
err = db.Get(context.Background()).RunInTx(func(db db.Handler) error {
|
||||
@@ -90,14 +90,14 @@ func setupUserAddCommand() *cobra.Command {
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
if user, err := userManager.CreateUser(username, displayName, password, homeID); err != nil {
|
||||
if user, err := userManager.CreateUser(email, displayName, password, homeID); err != nil {
|
||||
return err
|
||||
} else {
|
||||
u = user
|
||||
}
|
||||
|
||||
if homeID.Valid {
|
||||
if _, err := home.UpdatePermissions(u.Username, fs.PermissionRead|fs.PermissionWrite|fs.PermissionShare); err != nil {
|
||||
if _, err := home.UpdatePermissions(u.ID, fs.PermissionRead|fs.PermissionWrite|fs.PermissionShare); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func setupUserListCommand() *cobra.Command {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
for _, user := range users {
|
||||
logrus.Infof("%24s : %s", user.Username, user.DisplayName)
|
||||
logrus.Infof("%24s : %s", user.Email, user.DisplayName)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -14,14 +14,14 @@ import (
|
||||
|
||||
func setupUserModCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "mod username",
|
||||
Use: "mod email",
|
||||
Short: "Add User",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
username := args[0]
|
||||
u, err := user.ManagerFromContext(context.Background()).UserByUsername(username)
|
||||
email := args[0]
|
||||
u, err := user.ManagerFromContext(context.Background()).UserByEmail(email)
|
||||
if err != nil {
|
||||
fmt.Println("could not update user '" + username + "': " + err.Error())
|
||||
fmt.Println("could not update user '" + email + "': " + err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ func setupUserModCommand() *cobra.Command {
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("could not update user '" + username + "': " + err.Error())
|
||||
fmt.Println("could not update user '" + email + "': " + err.Error())
|
||||
os.Exit(3)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -13,12 +13,12 @@ import (
|
||||
|
||||
func setupUserPasswdCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "passwd username",
|
||||
Use: "passwd email",
|
||||
Short: "Change User Password",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
username := args[0]
|
||||
u, err := user.ManagerFromContext(context.Background()).UserByUsername(username)
|
||||
email := args[0]
|
||||
u, err := user.ManagerFromContext(context.Background()).UserByEmail(email)
|
||||
if err != nil {
|
||||
fmt.Println("unable to change password: " + err.Error())
|
||||
os.Exit(1)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
CREATE TABLE users(
|
||||
username TEXT PRIMARY KEY,
|
||||
id SERIAL PRIMARY KEY,
|
||||
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
modified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted TIMESTAMP,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
display_name TEXT NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
home uuid REFERENCES resources(id),
|
||||
|
||||
@@ -2,7 +2,7 @@ CREATE TABLE access_tokens(
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
expires TIMESTAMP NOT NULL,
|
||||
username TEXT NOT NULL REFERENCES users(username) ON UPDATE CASCADE ON DELETE CASCADE
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE
|
||||
);
|
||||
|
||||
---- create above / drop below ----
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
CREATE TABLE bookmarks(
|
||||
username TEXT NOT NULL REFERENCES users(username),
|
||||
user_id TEXT NOT NULL REFERENCES users(id),
|
||||
resource_id UUID NOT NULL REFERENCES resources(id),
|
||||
name TEXT NOT NULL,
|
||||
dir BOOLEAN NOT NULL,
|
||||
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
modified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted TIMESTAMP,
|
||||
PRIMARY KEY(username, resource_id)
|
||||
PRIMARY KEY(user_id, resource_id)
|
||||
);
|
||||
|
||||
CREATE INDEX modified_bookmarks ON bookmarks(username, modified);
|
||||
CREATE INDEX modified_bookmarks ON bookmarks(user_id, modified);
|
||||
|
||||
---- create above / drop below ----
|
||||
|
||||
|
||||
@@ -24,11 +24,11 @@ func (f filesystem) scanResourceAncestor(row pgx.CollectableRow) (ResourceAncest
|
||||
}
|
||||
|
||||
const ancestorsQuery = `WITH RECURSIVE nodes(id, name, parent, userPermission) AS (
|
||||
SELECT r.id, r.name, r.parent, COALESCE(r.permissions[$2::TEXT]::INTEGER, 0) as userPermission
|
||||
SELECT r.id, r.name, r.parent, COALESCE(r.permissions[$2::INT]::INT, 0) as userPermission
|
||||
FROM resources r
|
||||
WHERE r.id = $1::UUID
|
||||
UNION ALL
|
||||
SELECT r.id, r.name, r.parent, COALESCE(r.permissions[$2::TEXT]::INTEGER, 0) as userPermission
|
||||
SELECT r.id, r.name, r.parent, COALESCE(r.permissions[$2::INT]::INT, 0) as userPermission
|
||||
FROM resources r
|
||||
JOIN nodes n ON r.id = n.parent
|
||||
WHERE userPermission <> 0
|
||||
@@ -37,7 +37,7 @@ const ancestorsQuery = `WITH RECURSIVE nodes(id, name, parent, userPermission) A
|
||||
SELECT id, name, userPermission FROM nodes`
|
||||
|
||||
func (r Resource) GetAncestors() ([]ResourceAncestor, error) {
|
||||
if rows, err := r.f.db.Query(ancestorsQuery, r.id, r.f.username, r.f.fullAccess); err != nil {
|
||||
if rows, err := r.f.db.Query(ancestorsQuery, r.id, r.f.userID, r.f.fullAccess); err != nil {
|
||||
return nil, err
|
||||
} else if a, err := pgx.CollectRows(rows, r.f.scanResourceAncestor); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -10,7 +10,7 @@ type filesystem struct {
|
||||
db db.Handler
|
||||
cs storage.Storage
|
||||
pathRoot pgtype.UUID
|
||||
username string
|
||||
userID int32
|
||||
fullAccess bool
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ func (f filesystem) withDb(db db.Handler) filesystem {
|
||||
db: db,
|
||||
cs: f.cs,
|
||||
pathRoot: f.pathRoot,
|
||||
username: f.username,
|
||||
userID: f.userID,
|
||||
fullAccess: f.fullAccess,
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ func (f filesystem) withPathRoot(pathRoot pgtype.UUID) filesystem {
|
||||
db: f.db,
|
||||
cs: f.cs,
|
||||
pathRoot: pathRoot,
|
||||
username: f.username,
|
||||
userID: f.userID,
|
||||
fullAccess: f.fullAccess,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
func (f filesystem) ResourceByID(id uuid.UUID) (Resource, error) {
|
||||
const query = fullResourceQuery + "WHERE r.id = @id::UUID"
|
||||
args := pgx.NamedArgs{
|
||||
"username": f.username,
|
||||
"id": id,
|
||||
"user_id": f.userID,
|
||||
"id": id,
|
||||
}
|
||||
if rows, err := f.db.Query(query, args); err != nil {
|
||||
return Resource{}, err
|
||||
@@ -64,7 +64,7 @@ func (f filesystem) ResourceByPath(path string) (Resource, error) {
|
||||
|
||||
q := pg.Select(r.All(),
|
||||
pg.Select(goqu.L(publinkFieldsQuery)).From(l).Where(l.Col("root").Eq(r.Col("id"))),
|
||||
pg.Select(goqu.L("CASE WHEN COALESCE(p.permissions[?]::INTEGER, 0) <> 0 THEN p.id ELSE NULL END AS visible_parent", f.username)),
|
||||
pg.Select(goqu.L("CASE WHEN COALESCE(p.permissions[?]::INTEGER, 0) <> 0 THEN p.id ELSE NULL END AS visible_parent", f.userID)),
|
||||
pg.Select(goqu.L("COALESCE(p.permissions, '{}'::JSONB)")),
|
||||
).
|
||||
From(r).
|
||||
@@ -125,9 +125,9 @@ func (f filesystem) targetNameParentByPathWithRoot(path string, src Resource) (s
|
||||
func (f filesystem) childResourceByName(parentID uuid.UUID, name string) (Resource, error) {
|
||||
const query = fullResourceQuery + "WHERE r.parent = @parent::UUID AND r.name = @name::TEXT AND r.deleted IS NULL"
|
||||
args := pgx.NamedArgs{
|
||||
"username": f.username,
|
||||
"parent": parentID,
|
||||
"name": name,
|
||||
"user_id": f.userID,
|
||||
"parent": parentID,
|
||||
"name": name,
|
||||
}
|
||||
if rows, err := f.db.Query(query, args); err != nil {
|
||||
return Resource{}, err
|
||||
|
||||
@@ -62,11 +62,11 @@ type FileSystem interface {
|
||||
TrashEmpty() (int, error)
|
||||
}
|
||||
|
||||
func Open(ctx context.Context, username string, root pgtype.UUID, fullAccess bool) FileSystem {
|
||||
func Open(ctx context.Context, userID int32, root pgtype.UUID, fullAccess bool) FileSystem {
|
||||
return filesystem{
|
||||
db: db.Get(ctx),
|
||||
cs: storage.Get(),
|
||||
username: username,
|
||||
userID: userID,
|
||||
pathRoot: root,
|
||||
fullAccess: fullAccess,
|
||||
}
|
||||
@@ -74,7 +74,7 @@ func Open(ctx context.Context, username string, root pgtype.UUID, fullAccess boo
|
||||
|
||||
func OpenOmniscient(ctx context.Context) FileSystem {
|
||||
id := rootID()
|
||||
return Open(ctx, "", pgtype.UUID{Bytes: id, Valid: true}, true)
|
||||
return Open(ctx, -1, pgtype.UUID{Bytes: id, Valid: true}, true)
|
||||
}
|
||||
|
||||
func rootID() uuid.UUID {
|
||||
|
||||
@@ -15,7 +15,7 @@ const (
|
||||
PermissionSU = Permission(-1)
|
||||
)
|
||||
|
||||
func (r Resource) UpdatePermissions(username string, permission Permission) (Resource, error) {
|
||||
func (r Resource) UpdatePermissions(userID int32, permission Permission) (Resource, error) {
|
||||
if r.deleted.Valid {
|
||||
return r, ErrResourceDeleted
|
||||
}
|
||||
@@ -26,14 +26,14 @@ func (r Resource) UpdatePermissions(username string, permission Permission) (Res
|
||||
permission = permission & r.userPermission
|
||||
const qUpdate = `
|
||||
UPDATE resources SET
|
||||
grants[@username::TEXT] = jsonb_build_object('p', @permission::INT, 't', EXTRACT(EPOCH FROM NOW())::INTEGER),
|
||||
grants[@user_id::INT] = jsonb_build_object('p', @permission::INT, 't', EXTRACT(EPOCH FROM NOW())::INTEGER),
|
||||
modified = NOW()
|
||||
WHERE id = @resource_id::UUID
|
||||
RETURNING grants`
|
||||
|
||||
const qRemove = `
|
||||
UPDATE resources SET
|
||||
grants = grants - @username::TEXT,
|
||||
grants = grants - @user_id::INT,
|
||||
modified = NOW()
|
||||
WHERE id = @resource_id::UUID
|
||||
RETURNING grants`
|
||||
@@ -47,7 +47,7 @@ RETURNING grants`
|
||||
err := r.f.runInTx(func(f filesystem) error {
|
||||
row := f.db.QueryRow(q, pgx.NamedArgs{
|
||||
"resource_id": r.id,
|
||||
"username": username,
|
||||
"user_id": userID,
|
||||
"permission": permission,
|
||||
})
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ func (f filesystem) scanFullResource(row pgx.CollectableRow) (Resource, error) {
|
||||
permission := Permission(0)
|
||||
if f.fullAccess {
|
||||
permission = -1
|
||||
} else if p, err := readPermissionFromJson(r.permissions, f.username); err != nil {
|
||||
} else if p, err := readPermissionFromJson(r.permissions, f.userID); err != nil {
|
||||
return Resource{}, err
|
||||
} else {
|
||||
permission = p
|
||||
@@ -119,7 +119,7 @@ func (f filesystem) scanResourceWithoutParent(row pgx.CollectableRow) (Resource,
|
||||
permission := Permission(0)
|
||||
if f.fullAccess {
|
||||
permission = -1
|
||||
} else if p, err := readPermissionFromJson(r.permissions, f.username); err != nil {
|
||||
} else if p, err := readPermissionFromJson(r.permissions, f.userID); err != nil {
|
||||
return Resource{}, err
|
||||
} else {
|
||||
permission = p
|
||||
@@ -131,16 +131,16 @@ func (f filesystem) scanResourceWithoutParent(row pgx.CollectableRow) (Resource,
|
||||
return r, err
|
||||
}
|
||||
|
||||
func readPermissionFromJson(j []byte, username string) (Permission, error) {
|
||||
func readPermissionFromJson(j []byte, userID int32) (Permission, error) {
|
||||
if j == nil {
|
||||
return PermissionNone, nil
|
||||
}
|
||||
p := make(map[string]Permission)
|
||||
p := make(map[int32]Permission)
|
||||
err := json.Unmarshal(j, &p)
|
||||
if err != nil {
|
||||
return PermissionNone, err
|
||||
}
|
||||
if p, ok := p[username]; ok {
|
||||
if p, ok := p[userID]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return PermissionNone, nil
|
||||
|
||||
@@ -14,13 +14,13 @@ func (f filesystem) Search(query string, includeDeleted bool) ([]Resource, error
|
||||
qb.WriteString("\nAND r.deleted IS NULL")
|
||||
}
|
||||
if !f.fullAccess {
|
||||
qb.WriteString("\nAND r.permissions[@username::TEXT]::INTEGER <> 0")
|
||||
qb.WriteString("\nAND r.permissions[@user_id::INT]::INTEGER <> 0")
|
||||
}
|
||||
qb.WriteString("\nORDER BY word_similarity(f_prepare_search(r.name), @query::TEXT) DESC")
|
||||
|
||||
args := pgx.NamedArgs{
|
||||
"query": strings.ToLower(query),
|
||||
"username": f.username,
|
||||
"query": strings.ToLower(query),
|
||||
"user_id": f.userID,
|
||||
}
|
||||
|
||||
if rows, err := f.db.Query(qb.String(), args); err != nil {
|
||||
|
||||
@@ -6,13 +6,13 @@ import (
|
||||
)
|
||||
|
||||
func (f filesystem) SharedResources(excluded pgtype.UUID) ([]Resource, error) {
|
||||
const q = fullResourceQuery + `WHERE r.grants ? @username::TEXT
|
||||
const q = fullResourceQuery + `WHERE r.grants ? @user_id::INT
|
||||
AND r.id != @excluded::UUID
|
||||
AND r.deleted IS NULL
|
||||
AND (r.grants -> @username::TEXT -> 'p')::INTEGER <> 0
|
||||
ORDER BY r.grants -> @username::TEXT -> 't' DESC`
|
||||
AND (r.grants -> @user_id::INT -> 'p')::INTEGER <> 0
|
||||
ORDER BY r.grants -> @user_id::INT -> 't' DESC`
|
||||
args := pgx.NamedArgs{
|
||||
"username": f.username,
|
||||
"user_id": f.userID,
|
||||
"excluded": excluded,
|
||||
}
|
||||
if rows, err := f.db.Query(q, args); err != nil {
|
||||
|
||||
@@ -20,7 +20,7 @@ const publinkFieldsQuery = "COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT(" +
|
||||
|
||||
const fullResourceQuery = `SELECT r.*,
|
||||
(SELECT ` + publinkFieldsQuery + ` FROM publinks l WHERE l.root = r.id) AS links,
|
||||
CASE WHEN COALESCE(p.permissions[@username::TEXT]::INTEGER, 0) <> 0 THEN p.id ELSE NULL END AS visible_parent,
|
||||
CASE WHEN COALESCE(p.permissions[@user_id::INT]::INTEGER, 0) <> 0 THEN p.id ELSE NULL END AS visible_parent,
|
||||
COALESCE(p.permissions, '{}'::JSONB) AS inherited_permissions
|
||||
FROM resources r LEFT JOIN resources p ON p.id = r.parent
|
||||
`
|
||||
|
||||
@@ -22,11 +22,12 @@ func (f filesystem) TrashList(cursor string, n uint) ([]Resource, string, error)
|
||||
Select(
|
||||
r.All(),
|
||||
pg.Select(goqu.L(publinkFieldsQuery)).From(l).Where(l.Col("root").Eq(r.Col("id"))),
|
||||
pg.Select(goqu.L("CASE WHEN COALESCE(p.permissions[?]::INTEGER, 0) <> 0 THEN p.id ELSE NULL END AS visible_parent", f.username)),
|
||||
// TODO: Always select p.id when fullAccess
|
||||
pg.Select(goqu.L("CASE WHEN COALESCE(p.permissions[?]::INTEGER, 0) <> 0 THEN p.id ELSE NULL END AS visible_parent", f.userID)),
|
||||
pg.Select(goqu.L("COALESCE(p.permissions, '{}'::JSONB)")),
|
||||
)
|
||||
if !f.fullAccess {
|
||||
q = q.Where(goqu.L("r.permissions[?]::INTEGER <> 0", f.username))
|
||||
q = q.Where(goqu.L("r.permissions[?]::INTEGER <> 0", f.userID))
|
||||
}
|
||||
if cursor != "" {
|
||||
if d, err := base64.StdEncoding.DecodeString(cursor); err != nil {
|
||||
|
||||
@@ -10,7 +10,7 @@ func (f filesystem) selectDeletedResources() *goqu.SelectDataset {
|
||||
From("resources").
|
||||
Where(goqu.C("deleted").IsNotNull())
|
||||
if !f.fullAccess {
|
||||
q = q.Where(goqu.L("permissions[?]::INTEGER <> 0", f.username))
|
||||
q = q.Where(goqu.L("permissions[?]::INTEGER <> 0", f.userID))
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ var accessTokenValidity = pgtype.Interval{
|
||||
|
||||
var ErrCredentialsInvalid = errors.NewError(http.StatusUnauthorized, "credentials_invalid", "invalid Credentials")
|
||||
|
||||
func (m manager) VerifyUserPassword(username, password string) (User, error) {
|
||||
if user, err := m.UserByUsername(username); err != nil {
|
||||
func (m manager) VerifyUserPassword(email, password string) (User, error) {
|
||||
if user, err := m.UserByEmail(email); err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return User{}, ErrCredentialsInvalid
|
||||
}
|
||||
@@ -39,10 +39,10 @@ func (m manager) VerifyUserPassword(username, password string) (User, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m manager) CreateAccessToken(username string) (string, error) {
|
||||
const q = `INSERT INTO access_tokens(id, expires, username) VALUES ($1::text, NOW() + $2::interval, $3::text)`
|
||||
func (m manager) CreateAccessToken(userID string) (string, error) {
|
||||
const q = `INSERT INTO access_tokens(id, expires, user_id) VALUES ($1::TEXT, NOW() + $2::INTERVAL, $3::INT)`
|
||||
id := GenerateRandomString(accessTokenLength)
|
||||
if _, err := m.db.Exec(q, id, accessTokenValidity, username); err != nil {
|
||||
if _, err := m.db.Exec(q, id, accessTokenValidity, userID); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return id, nil
|
||||
@@ -50,15 +50,16 @@ func (m manager) CreateAccessToken(username string) (string, error) {
|
||||
}
|
||||
|
||||
func (m manager) ReadAccessToken(accessToken string) (User, error) {
|
||||
const q = `SELECT t.expires, u.username, u.display_name, u.permissions, u.home FROM access_tokens t JOIN users u ON t.username = u.username WHERE t.id = $1; `
|
||||
const q = `SELECT t.expires, u.id, u.email, u.display_name, u.permissions, u.home FROM access_tokens t JOIN users u ON t.user_id = u.id WHERE t.id = $1; `
|
||||
row := m.db.QueryRow(q, accessToken)
|
||||
|
||||
var expires pgtype.Timestamp
|
||||
var username string
|
||||
var userID int32
|
||||
var email string
|
||||
var displayName string
|
||||
var permissions int32
|
||||
var home pgtype.UUID
|
||||
if err := row.Scan(&expires, &username, &displayName, &permissions, &home); err != nil {
|
||||
if err := row.Scan(&expires, &userID, &email, &displayName, &permissions, &home); err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
err = ErrCredentialsInvalid
|
||||
}
|
||||
@@ -68,9 +69,10 @@ func (m manager) ReadAccessToken(accessToken string) (User, error) {
|
||||
} else {
|
||||
return User{
|
||||
BasicUser: BasicUser{
|
||||
Username: username,
|
||||
Email: email,
|
||||
DisplayName: displayName,
|
||||
},
|
||||
ID: userID,
|
||||
Permissions: permissions,
|
||||
Home: home,
|
||||
}, nil
|
||||
|
||||
@@ -36,9 +36,9 @@ func (m manager) AddBookmark(u User, resource fs.Resource, name string) (Bookmar
|
||||
return Bookmark{}, fs.ErrResourceNameInvalid
|
||||
}
|
||||
|
||||
const q = `INSERT INTO bookmarks(username, resource_id, name, dir)
|
||||
VALUES ($1::TEXT, $2::UUID, $3::TEXT, $4::BOOLEAN)
|
||||
ON CONFLICT(username, resource_id) DO UPDATE
|
||||
const q = `INSERT INTO bookmarks(user_id, resource_id, name, dir)
|
||||
VALUES ($1::INT, $2::UUID, $3::TEXT, $4::BOOLEAN)
|
||||
ON CONFLICT(user_id, resource_id) DO UPDATE
|
||||
SET
|
||||
created = CASE WHEN bookmarks.deleted IS NULL THEN CURRENT_TIMESTAMP ELSE bookmarks.created END,
|
||||
modified = CURRENT_TIMESTAMP,
|
||||
@@ -46,7 +46,7 @@ SET
|
||||
name = $3::TEXT
|
||||
RETURNING resource_id, name, dir, created`
|
||||
|
||||
if rows, err := m.db.Query(q, u.Username, resource.ID(), name, resource.Dir()); err != nil {
|
||||
if rows, err := m.db.Query(q, u.ID, resource.ID(), name, resource.Dir()); err != nil {
|
||||
return Bookmark{}, err
|
||||
} else if bookmark, err := pgx.CollectExactlyOneRow(rows, scanBookmark); err != nil {
|
||||
return Bookmark{}, err
|
||||
@@ -56,19 +56,19 @@ RETURNING resource_id, name, dir, created`
|
||||
}
|
||||
|
||||
func (m manager) RemoveBookmark(u User, id uuid.UUID) error {
|
||||
const q = "DELETE FROM bookmarks WHERE username = $1::TEXT AND resource_id = $2::UUID"
|
||||
_, err := m.db.Exec(q, u.Username, id)
|
||||
const q = "DELETE FROM bookmarks WHERE user_id = $1::INT AND resource_id = $2::UUID"
|
||||
_, err := m.db.Exec(q, u.ID, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m manager) ListBookmarks(u User) ([]Bookmark, error) {
|
||||
const q = `SELECT resource_id, name, dir, created
|
||||
FROM bookmarks b
|
||||
WHERE username = $1::TEXT
|
||||
WHERE user_id = $1::INT
|
||||
AND deleted IS NULL
|
||||
ORDER BY modified DESC`
|
||||
|
||||
if rows, err := m.db.Query(q, u.Username); err != nil {
|
||||
if rows, err := m.db.Query(q, u.ID); err != nil {
|
||||
return nil, err
|
||||
} else if bookmarks, err := pgx.CollectRows(rows, scanBookmark); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -12,14 +12,14 @@ import (
|
||||
|
||||
var ErrUserExists = errors.NewError(http.StatusNotFound, "user_already_exists", "user already exists")
|
||||
|
||||
func (m manager) CreateUser(username, displayName, password string, home pgtype.UUID) (User, error) {
|
||||
const q = ` INSERT INTO users(username, display_name, password_hash, home)
|
||||
func (m manager) CreateUser(email, displayName, password string, home pgtype.UUID) (User, error) {
|
||||
const q = ` INSERT INTO users(email, display_name, password_hash, home)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING username, display_name, password_hash, home, permissions`
|
||||
RETURNING id, email, display_name, password_hash, home, permissions`
|
||||
|
||||
if hash, err := crypt.GenerateArgon2EncodedHash(password); err != nil {
|
||||
return User{}, err
|
||||
} else if rows, err := m.db.Query(q, username, displayName, hash, home); err != nil {
|
||||
} else if rows, err := m.db.Query(q, email, displayName, hash, home); err != nil {
|
||||
if strings.Contains(err.Error(), "users_pkey") {
|
||||
err = ErrUserExists
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ func (m manager) ListUsers(since *time.Time) ([]BasicUser, error) {
|
||||
if since != nil {
|
||||
s.Time = *since
|
||||
}
|
||||
const q = "SELECT username, display_name FROM users WHERE modified > $1::TIMESTAMP"
|
||||
const q = "SELECT email, display_name FROM users WHERE modified > $1::TIMESTAMP"
|
||||
if rows, err := m.db.Query(q, s); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
@@ -26,9 +26,9 @@ func (m manager) ListUsers(since *time.Time) ([]BasicUser, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m manager) UserByUsername(username string) (User, error) {
|
||||
const q = "SELECT username, display_name, password_hash, home, permissions FROM users WHERE username = $1"
|
||||
if rows, err := m.db.Query(q, username); err != nil {
|
||||
func (m manager) UserByEmail(email string) (User, error) {
|
||||
const q = "SELECT id, email, display_name, password_hash, home, permissions FROM users WHERE email = $1"
|
||||
if rows, err := m.db.Query(q, email); err != nil {
|
||||
return User{}, err
|
||||
} else if u, err := pgx.CollectExactlyOneRow(rows, scanUser); err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
@@ -40,9 +40,9 @@ func (m manager) UserByUsername(username string) (User, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m manager) UserHome(username string) (pgtype.UUID, error) {
|
||||
const q = "SELECT home FROM users WHERE username = $1"
|
||||
row := m.db.QueryRow(q, username)
|
||||
func (m manager) UserHome(email string) (pgtype.UUID, error) {
|
||||
const q = "SELECT home FROM users WHERE email = $1"
|
||||
row := m.db.QueryRow(q, email)
|
||||
var id pgtype.UUID
|
||||
if err := row.Scan(&id); err == nil {
|
||||
if !id.Valid {
|
||||
|
||||
@@ -6,27 +6,27 @@ import (
|
||||
)
|
||||
|
||||
func (m manager) UpdateUserHome(user User, home pgtype.UUID) error {
|
||||
const q = "UPDATE users SET home = $2::UUID, modified = NOW() WHERE username = $1::TEXT"
|
||||
if _, err := m.db.Exec(q, user.Username, home); err != nil {
|
||||
const q = "UPDATE users SET home = $2::UUID, modified = NOW() WHERE id = $1::INT"
|
||||
if _, err := m.db.Exec(q, user.ID, home); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m manager) UpdateUserDisplayName(user User, displayName string) error {
|
||||
const q = "UPDATE users SET display_name = $2::UUID, modified = NOW() WHERE username = $1::TEXT"
|
||||
if _, err := m.db.Exec(q, user.Username, displayName); err != nil {
|
||||
const q = "UPDATE users SET display_name = $2::UUID, modified = NOW() WHERE id = $1::INT"
|
||||
if _, err := m.db.Exec(q, user.ID, displayName); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m manager) UpdateUserPassword(user User, password string) error {
|
||||
const q = "UPDATE users SET password_hash = $2::TEXT, modified = NOW() WHERE username = $1::TEXT"
|
||||
const q = "UPDATE users SET password_hash = $2::TEXT, modified = NOW() WHERE id = $1::INT"
|
||||
if hash, err := crypt.GenerateArgon2EncodedHash(password); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if _, err := m.db.Exec(q, user.Username, hash); err != nil {
|
||||
if _, err := m.db.Exec(q, user.ID, hash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -34,16 +34,16 @@ func (m manager) UpdateUserPassword(user User, password string) error {
|
||||
}
|
||||
|
||||
func (m manager) GrantUserPermissions(user User, permissions int) error {
|
||||
const q = "UPDATE users SET permissions = permissions | $2::INTEGER, modified = NOW() WHERE username = $1::UUID"
|
||||
if _, err := m.db.Exec(q, user.Username, permissions); err != nil {
|
||||
const q = "UPDATE users SET permissions = permissions | $2::INTEGER, modified = NOW() WHERE id = $1::INT"
|
||||
if _, err := m.db.Exec(q, user.ID, permissions); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m manager) RevokeUserPermissions(user User, permissions int) error {
|
||||
const q = "UPDATE users SET permissions = permissions & ~ $2::INTEGER, modified = NOW() WHERE username = $1::UUID"
|
||||
if _, err := m.db.Exec(q, user.Username, permissions); err != nil {
|
||||
const q = "UPDATE users SET permissions = permissions & ~ $2::INTEGER, modified = NOW() WHERE id = $1::INT"
|
||||
if _, err := m.db.Exec(q, user.ID, permissions); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -11,12 +11,13 @@ import (
|
||||
)
|
||||
|
||||
type BasicUser struct {
|
||||
Username string
|
||||
Email string
|
||||
DisplayName string
|
||||
}
|
||||
|
||||
type User struct {
|
||||
BasicUser
|
||||
ID int32
|
||||
PasswordHash string
|
||||
Home pgtype.UUID
|
||||
Permissions int32
|
||||
@@ -25,7 +26,7 @@ type User struct {
|
||||
func scanBasicUser(row pgx.CollectableRow) (BasicUser, error) {
|
||||
var u BasicUser
|
||||
err := row.Scan(
|
||||
&u.Username,
|
||||
&u.Email,
|
||||
&u.DisplayName,
|
||||
)
|
||||
return u, err
|
||||
@@ -34,7 +35,8 @@ func scanBasicUser(row pgx.CollectableRow) (BasicUser, error) {
|
||||
func scanUser(row pgx.CollectableRow) (User, error) {
|
||||
var u User
|
||||
err := row.Scan(
|
||||
&u.Username,
|
||||
&u.ID,
|
||||
&u.Email,
|
||||
&u.DisplayName,
|
||||
&u.PasswordHash,
|
||||
&u.Home,
|
||||
@@ -44,17 +46,17 @@ func scanUser(row pgx.CollectableRow) (User, error) {
|
||||
}
|
||||
|
||||
func (u User) OpenFileSystem(ctx context.Context) fs.FileSystem {
|
||||
return fs.Open(ctx, u.Username, u.Home, u.Permissions&PermissionAllFiles != 0)
|
||||
return fs.Open(ctx, u.ID, u.Home, u.Permissions&PermissionAllFiles != 0)
|
||||
}
|
||||
|
||||
type Manager interface {
|
||||
// create.go
|
||||
CreateUser(username, displayName, password string, home pgtype.UUID) (User, error)
|
||||
CreateUser(email, displayName, password string, home pgtype.UUID) (User, error)
|
||||
|
||||
// select.go
|
||||
ListUsers(since *time.Time) ([]BasicUser, error)
|
||||
UserByUsername(email string) (User, error)
|
||||
UserHome(username string) (pgtype.UUID, error)
|
||||
UserByEmail(email string) (User, error)
|
||||
UserHome(email string) (pgtype.UUID, error)
|
||||
|
||||
// update.go
|
||||
UpdateUserHome(user User, home pgtype.UUID) error
|
||||
@@ -63,7 +65,7 @@ type Manager interface {
|
||||
|
||||
// auth.go
|
||||
VerifyUserPassword(email, password string) (User, error)
|
||||
CreateAccessToken(username string) (string, error)
|
||||
CreateAccessToken(email string) (string, error)
|
||||
ReadAccessToken(accessToken string) (User, error)
|
||||
|
||||
// bookmarks.go
|
||||
|
||||
Reference in New Issue
Block a user