feat(sharing): allow custom share IDs (#2353)

feat: allow custom share IDs

   - Change sharing ID column from char(12) to varchar(64)
   - Add new_id field to UpdateSharingReq for renaming share IDs
   - Add ID validation (max 64 chars, alphanumeric/CJK/hyphens/underscores)
   - Add conflict check when updating share ID
   - Add customize_share_id permission (bit 15)

   Closes OpenListTeam/OpenList#1806
This commit is contained in:
Miao Zhao
2026-05-03 11:06:28 +08:00
committed by GitHub
parent e28406fa2c
commit 7e37c40516
5 changed files with 62 additions and 2 deletions

View File

@@ -64,6 +64,14 @@ func UpdateSharing(s *model.SharingDB) error {
return errors.WithStack(db.Save(s).Error)
}
func UpdateSharingId(oldId, newId string) error {
// Check if new ID already exists
if err := db.Where("id = ?", newId).First(&model.SharingDB{}).Error; err == nil {
return errors.New("sharing id already exists")
}
return errors.WithStack(db.Model(&model.SharingDB{}).Where("id = ?", oldId).Update("id", newId).Error)
}
func DeleteSharingById(id string) error {
s := model.SharingDB{ID: id}
return errors.WithStack(db.Where(s).Delete(&s).Error)

View File

@@ -3,7 +3,7 @@ package model
import "time"
type SharingDB struct {
ID string `json:"id" gorm:"type:char(12);primaryKey"`
ID string `json:"id" gorm:"type:varchar(64);primaryKey"`
FilesRaw string `json:"-" gorm:"type:text"`
Expires *time.Time `json:"expires"`
Pwd string `json:"pwd"`

View File

@@ -63,6 +63,7 @@ type User struct {
// 12: can read archives
// 13: can decompress archives
// 14: can share
// 15: can customize share id
Permission int32 `json:"permission"`
OtpSecret string `json:"-"`
SsoID string `json:"sso_id"` // unique by sso platform
@@ -219,6 +220,14 @@ func (u *User) CanShare() bool {
return CanShare(u.Permission)
}
func CanCustomizeShareID(permission int32) bool {
return (permission>>15)&1 == 1
}
func (u *User) CanCustomizeShareID() bool {
return CanCustomizeShareID(u.Permission)
}
func (u *User) JoinPath(reqPath string) (string, error) {
return utils.JoinBasePath(u.BasePath, reqPath)
}

View File

@@ -133,6 +133,15 @@ func UpdateSharing(sharing *model.Sharing, skipMarshal ...bool) (err error) {
return db.UpdateSharing(sharing.SharingDB)
}
func UpdateSharingId(sharing *model.Sharing, newId string) error {
sharingCache.Del(sharing.ID)
if err := db.UpdateSharingId(sharing.ID, newId); err != nil {
return err
}
sharing.ID = newId
return nil
}
func DeleteSharing(sid string) error {
sharingCache.Del(sid)
return db.DeleteSharingById(sid)

View File

@@ -3,6 +3,7 @@ package handles
import (
"fmt"
stdpath "path"
"regexp"
"strings"
"time"
@@ -416,6 +417,19 @@ type UpdateSharingReq struct {
CreatorName string `json:"creator"`
Accessed int `json:"accessed"`
ID string `json:"id"`
NewID string `json:"new_id"`
}
var validSharingID = regexp.MustCompile(`^[\w\p{Han}\-]+$`)
func validateSharingID(id string) error {
if len([]rune(id)) > 64 {
return errors.New("share id must be at most 64 characters")
}
if !validSharingID.MatchString(id) {
return errors.New("share id can only contain letters, numbers, underscores, hyphens, and CJK characters")
}
return nil
}
func UpdateSharing(c *gin.Context) {
@@ -471,6 +485,20 @@ func UpdateSharing(c *gin.Context) {
s.Readme = req.Readme
s.Remark = req.Remark
s.Creator = user
if req.NewID != "" && req.NewID != req.ID {
if !reqUser.CanCustomizeShareID() {
common.ErrorStrResp(c, "permission denied", 403)
return
}
if err = validateSharingID(req.NewID); err != nil {
common.ErrorResp(c, err, 400)
return
}
if err = op.UpdateSharingId(s, req.NewID); err != nil {
common.ErrorResp(c, err, 500)
return
}
}
if err = op.UpdateSharing(s); err != nil {
common.ErrorResp(c, err, 500)
} else {
@@ -493,6 +521,12 @@ func CreateSharing(c *gin.Context) {
common.ErrorStrResp(c, "must add at least 1 object", 400)
return
}
if req.ID != "" {
if err = validateSharingID(req.ID); err != nil {
common.ErrorResp(c, err, 400)
return
}
}
var user *model.User
reqUser := c.Request.Context().Value(conf.UserKey).(*model.User)
if reqUser.IsAdmin() && req.CreatorName != "" {
@@ -503,7 +537,7 @@ func CreateSharing(c *gin.Context) {
}
} else {
user = reqUser
if !user.CanShare() || (!user.IsAdmin() && req.ID != "") {
if !user.CanShare() || (!user.CanCustomizeShareID() && req.ID != "") {
common.ErrorStrResp(c, "permission denied", 403)
return
}