feat(boxstore): add configurable expiry for one-time downloads
Introduces a new configuration setting `one_time_download_expiry_seconds` to allow administrators to define a default expiration period for one-time downloads. The retention logic in `boxstore` has been updated to use this global expiry value when a box is marked as a one-time download and no specific retention period is defined in the manifest.
This commit is contained in:
@@ -31,8 +31,9 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
uploadRoot = filepath.Join("data", "uploads")
|
||||
manifestMu sync.Mutex
|
||||
uploadRoot = filepath.Join("data", "uploads")
|
||||
oneTimeDownloadExpiry int64
|
||||
manifestMu sync.Mutex
|
||||
)
|
||||
|
||||
var retentionOptions = []models.RetentionOption{
|
||||
@@ -70,6 +71,10 @@ func SetUploadRoot(path string) {
|
||||
uploadRoot = filepath.Clean(path)
|
||||
}
|
||||
|
||||
func SetOneTimeDownloadExpiry(seconds int64) {
|
||||
oneTimeDownloadExpiry = seconds
|
||||
}
|
||||
|
||||
func UploadRoot() string {
|
||||
return uploadRoot
|
||||
}
|
||||
@@ -638,7 +643,17 @@ func startRetentionIfTerminalUnlocked(manifest *models.BoxManifest) {
|
||||
if !manifest.ExpiresAt.IsZero() || len(manifest.Files) == 0 {
|
||||
return
|
||||
}
|
||||
if manifest.OneTimeDownload {
|
||||
|
||||
seconds := manifest.RetentionSecs
|
||||
if seconds <= 0 {
|
||||
if manifest.OneTimeDownload {
|
||||
seconds = oneTimeDownloadExpiry
|
||||
} else {
|
||||
seconds = normalizeRetentionOption(manifest.RetentionKey).Seconds
|
||||
}
|
||||
}
|
||||
|
||||
if manifest.OneTimeDownload && seconds <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -648,10 +663,7 @@ func startRetentionIfTerminalUnlocked(manifest *models.BoxManifest) {
|
||||
}
|
||||
}
|
||||
|
||||
seconds := manifest.RetentionSecs
|
||||
if seconds <= 0 {
|
||||
seconds = normalizeRetentionOption(manifest.RetentionKey).Seconds
|
||||
}
|
||||
// seconds is already handled above
|
||||
|
||||
// Retention starts after uploads settle so slow or very large uploads do
|
||||
// not expire before users get a real chance to open the box.
|
||||
|
||||
@@ -26,23 +26,24 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
SettingGuestUploadsEnabled = "guest_uploads_enabled"
|
||||
SettingAPIEnabled = "api_enabled"
|
||||
SettingZipDownloadsEnabled = "zip_downloads_enabled"
|
||||
SettingOneTimeDownloadsEnabled = "one_time_downloads_enabled"
|
||||
SettingRenewOnAccessEnabled = "renew_on_access_enabled"
|
||||
SettingRenewOnDownloadEnabled = "renew_on_download_enabled"
|
||||
SettingDefaultGuestExpirySecs = "default_guest_expiry_seconds"
|
||||
SettingMaxGuestExpirySecs = "max_guest_expiry_seconds"
|
||||
SettingGlobalMaxFileSizeBytes = "global_max_file_size_bytes"
|
||||
SettingGlobalMaxBoxSizeBytes = "global_max_box_size_bytes"
|
||||
SettingDefaultUserMaxFileBytes = "default_user_max_file_size_bytes"
|
||||
SettingDefaultUserMaxBoxBytes = "default_user_max_box_size_bytes"
|
||||
SettingSessionTTLSeconds = "session_ttl_seconds"
|
||||
SettingBoxPollIntervalMS = "box_poll_interval_ms"
|
||||
SettingThumbnailBatchSize = "thumbnail_batch_size"
|
||||
SettingThumbnailIntervalSeconds = "thumbnail_interval_seconds"
|
||||
SettingDataDir = "data_dir"
|
||||
SettingGuestUploadsEnabled = "guest_uploads_enabled"
|
||||
SettingAPIEnabled = "api_enabled"
|
||||
SettingZipDownloadsEnabled = "zip_downloads_enabled"
|
||||
SettingOneTimeDownloadsEnabled = "one_time_downloads_enabled"
|
||||
SettingOneTimeDownloadExpirySecs = "one_time_download_expiry_seconds"
|
||||
SettingRenewOnAccessEnabled = "renew_on_access_enabled"
|
||||
SettingRenewOnDownloadEnabled = "renew_on_download_enabled"
|
||||
SettingDefaultGuestExpirySecs = "default_guest_expiry_seconds"
|
||||
SettingMaxGuestExpirySecs = "max_guest_expiry_seconds"
|
||||
SettingGlobalMaxFileSizeBytes = "global_max_file_size_bytes"
|
||||
SettingGlobalMaxBoxSizeBytes = "global_max_box_size_bytes"
|
||||
SettingDefaultUserMaxFileBytes = "default_user_max_file_size_bytes"
|
||||
SettingDefaultUserMaxBoxBytes = "default_user_max_box_size_bytes"
|
||||
SettingSessionTTLSeconds = "session_ttl_seconds"
|
||||
SettingBoxPollIntervalMS = "box_poll_interval_ms"
|
||||
SettingThumbnailBatchSize = "thumbnail_batch_size"
|
||||
SettingThumbnailIntervalSeconds = "thumbnail_interval_seconds"
|
||||
SettingDataDir = "data_dir"
|
||||
)
|
||||
|
||||
type SettingType string
|
||||
@@ -82,12 +83,13 @@ type Config struct {
|
||||
AdminCookieSecure bool
|
||||
AllowAdminSettingsOverride bool
|
||||
|
||||
GuestUploadsEnabled bool
|
||||
APIEnabled bool
|
||||
ZipDownloadsEnabled bool
|
||||
OneTimeDownloadsEnabled bool
|
||||
RenewOnAccessEnabled bool
|
||||
RenewOnDownloadEnabled bool
|
||||
GuestUploadsEnabled bool
|
||||
APIEnabled bool
|
||||
ZipDownloadsEnabled bool
|
||||
OneTimeDownloadsEnabled bool
|
||||
OneTimeDownloadExpirySeconds int64
|
||||
RenewOnAccessEnabled bool
|
||||
RenewOnDownloadEnabled bool
|
||||
|
||||
DefaultGuestExpirySeconds int64
|
||||
MaxGuestExpirySeconds int64
|
||||
@@ -110,6 +112,7 @@ var Definitions = []SettingDefinition{
|
||||
{Key: SettingAPIEnabled, EnvName: "WARPBOX_API_ENABLED", Label: "API enabled", Type: SettingTypeBool, Editable: true},
|
||||
{Key: SettingZipDownloadsEnabled, EnvName: "WARPBOX_ZIP_DOWNLOADS_ENABLED", Label: "ZIP downloads enabled", Type: SettingTypeBool, Editable: true},
|
||||
{Key: SettingOneTimeDownloadsEnabled, EnvName: "WARPBOX_ONE_TIME_DOWNLOADS_ENABLED", Label: "One-time downloads enabled", Type: SettingTypeBool, Editable: true},
|
||||
{Key: SettingOneTimeDownloadExpirySecs, EnvName: "WARPBOX_ONE_TIME_DOWNLOAD_EXPIRY_SECONDS", Label: "One-time download expiry seconds", Type: SettingTypeInt64, Editable: true, Minimum: 0},
|
||||
{Key: SettingRenewOnAccessEnabled, EnvName: "WARPBOX_RENEW_ON_ACCESS_ENABLED", Label: "Renew on access enabled", Type: SettingTypeBool, Editable: true},
|
||||
{Key: SettingRenewOnDownloadEnabled, EnvName: "WARPBOX_RENEW_ON_DOWNLOAD_ENABLED", Label: "Renew on download enabled", Type: SettingTypeBool, Editable: true},
|
||||
{Key: SettingDefaultGuestExpirySecs, EnvName: "WARPBOX_DEFAULT_GUEST_EXPIRY_SECONDS", Label: "Default guest expiry seconds", Type: SettingTypeInt64, Editable: true, Minimum: 0},
|
||||
@@ -126,22 +129,23 @@ var Definitions = []SettingDefinition{
|
||||
|
||||
func Load() (*Config, error) {
|
||||
cfg := &Config{
|
||||
DataDir: "./data",
|
||||
AdminUsername: "admin",
|
||||
AdminEnabled: AdminEnabledAuto,
|
||||
AllowAdminSettingsOverride: true,
|
||||
GuestUploadsEnabled: true,
|
||||
APIEnabled: true,
|
||||
ZipDownloadsEnabled: true,
|
||||
OneTimeDownloadsEnabled: true,
|
||||
DefaultGuestExpirySeconds: 10,
|
||||
MaxGuestExpirySeconds: 48 * 60 * 60,
|
||||
SessionTTLSeconds: 24 * 60 * 60,
|
||||
BoxPollIntervalMS: 5000,
|
||||
ThumbnailBatchSize: 10,
|
||||
ThumbnailIntervalSeconds: 30,
|
||||
sources: make(map[string]Source),
|
||||
values: make(map[string]string),
|
||||
DataDir: "./data",
|
||||
AdminUsername: "admin",
|
||||
AdminEnabled: AdminEnabledAuto,
|
||||
AllowAdminSettingsOverride: true,
|
||||
GuestUploadsEnabled: true,
|
||||
APIEnabled: true,
|
||||
ZipDownloadsEnabled: true,
|
||||
OneTimeDownloadsEnabled: true,
|
||||
OneTimeDownloadExpirySeconds: 7 * 24 * 60 * 60,
|
||||
DefaultGuestExpirySeconds: 10,
|
||||
MaxGuestExpirySeconds: 48 * 60 * 60,
|
||||
SessionTTLSeconds: 24 * 60 * 60,
|
||||
BoxPollIntervalMS: 5000,
|
||||
ThumbnailBatchSize: 10,
|
||||
ThumbnailIntervalSeconds: 30,
|
||||
sources: make(map[string]Source),
|
||||
values: make(map[string]string),
|
||||
}
|
||||
|
||||
cfg.captureDefaults()
|
||||
@@ -198,6 +202,7 @@ func Load() (*Config, error) {
|
||||
}{
|
||||
{SettingDefaultGuestExpirySecs, "WARPBOX_DEFAULT_GUEST_EXPIRY_SECONDS", 0, &cfg.DefaultGuestExpirySeconds},
|
||||
{SettingMaxGuestExpirySecs, "WARPBOX_MAX_GUEST_EXPIRY_SECONDS", 0, &cfg.MaxGuestExpirySeconds},
|
||||
{SettingOneTimeDownloadExpirySecs, "WARPBOX_ONE_TIME_DOWNLOAD_EXPIRY_SECONDS", 0, &cfg.OneTimeDownloadExpirySeconds},
|
||||
{SettingSessionTTLSeconds, "WARPBOX_SESSION_TTL_SECONDS", 60, &cfg.SessionTTLSeconds},
|
||||
}
|
||||
for _, item := range envInt64s {
|
||||
@@ -359,6 +364,7 @@ func (cfg *Config) captureDefaults() {
|
||||
cfg.setValue(SettingAPIEnabled, formatBool(cfg.APIEnabled), SourceDefault)
|
||||
cfg.setValue(SettingZipDownloadsEnabled, formatBool(cfg.ZipDownloadsEnabled), SourceDefault)
|
||||
cfg.setValue(SettingOneTimeDownloadsEnabled, formatBool(cfg.OneTimeDownloadsEnabled), SourceDefault)
|
||||
cfg.setValue(SettingOneTimeDownloadExpirySecs, strconv.FormatInt(cfg.OneTimeDownloadExpirySeconds, 10), SourceDefault)
|
||||
cfg.setValue(SettingRenewOnAccessEnabled, formatBool(cfg.RenewOnAccessEnabled), SourceDefault)
|
||||
cfg.setValue(SettingRenewOnDownloadEnabled, formatBool(cfg.RenewOnDownloadEnabled), SourceDefault)
|
||||
cfg.setValue(SettingDefaultGuestExpirySecs, strconv.FormatInt(cfg.DefaultGuestExpirySeconds, 10), SourceDefault)
|
||||
@@ -485,6 +491,8 @@ func (cfg *Config) assignInt64(key string, value int64, source Source) {
|
||||
cfg.DefaultGuestExpirySeconds = value
|
||||
case SettingMaxGuestExpirySecs:
|
||||
cfg.MaxGuestExpirySeconds = value
|
||||
case SettingOneTimeDownloadExpirySecs:
|
||||
cfg.OneTimeDownloadExpirySeconds = value
|
||||
case SettingDefaultUserMaxFileBytes:
|
||||
cfg.DefaultUserMaxFileSizeBytes = value
|
||||
case SettingDefaultUserMaxBoxBytes:
|
||||
|
||||
@@ -53,6 +53,7 @@ type BoxManifest struct {
|
||||
AuthToken string `json:"auth_token,omitempty"`
|
||||
DisableZip bool `json:"disable_zip,omitempty"`
|
||||
OneTimeDownload bool `json:"one_time_download,omitempty"`
|
||||
Consumed bool `json:"consumed,omitempty"`
|
||||
}
|
||||
|
||||
type BoxSummary struct {
|
||||
|
||||
@@ -211,8 +211,14 @@ func (app *App) handleOneTimeDownloadBox(ctx *gin.Context, boxID string) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !hasManifest || !manifest.OneTimeDownload {
|
||||
ctx.String(http.StatusNotFound, "Box not found")
|
||||
if !hasManifest || !manifest.OneTimeDownload || manifest.Consumed {
|
||||
ctx.String(http.StatusGone, "Box already consumed")
|
||||
return
|
||||
}
|
||||
|
||||
manifest.Consumed = true
|
||||
if err := boxstore.WriteManifest(boxID, manifest); err != nil {
|
||||
ctx.String(http.StatusInternalServerError, "Could not mark box as consumed")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -327,7 +333,12 @@ func (app *App) handleDownloadThumbnail(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, _, authorized := app.authorizeBoxRequest(ctx, boxID, true); !authorized {
|
||||
manifest, hasManifest, authorized := app.authorizeBoxRequest(ctx, boxID, true)
|
||||
if !authorized {
|
||||
return
|
||||
}
|
||||
if hasManifest && manifest.OneTimeDownload {
|
||||
ctx.String(http.StatusForbidden, "Thumbnails disabled for one-time boxes")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ func Run(addr string) error {
|
||||
}
|
||||
|
||||
boxstore.SetUploadRoot(cfg.UploadsDir)
|
||||
boxstore.SetOneTimeDownloadExpiry(cfg.OneTimeDownloadExpirySeconds)
|
||||
|
||||
store, err := metastore.Open(cfg.DBDir)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user