feat: add default share duration setting for one-click shares

This commit is contained in:
Yeicor 2023-11-04 19:07:25 +01:00
parent a4cb813ddf
commit 846f72ebbc
14 changed files with 177 additions and 93 deletions

View File

@ -7,6 +7,7 @@ import (
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"strconv"
"strings" "strings"
"github.com/filebrowser/filebrowser/v2/errors" "github.com/filebrowser/filebrowser/v2/errors"
@ -156,16 +157,18 @@ func (a *HookAuth) SaveUser() (*users.User, error) {
// create user with the provided credentials // create user with the provided credentials
d := &users.User{ d := &users.User{
Username: a.Cred.Username, Username: a.Cred.Username,
Password: pass, Password: pass,
Scope: a.Settings.Defaults.Scope, Scope: a.Settings.Defaults.Scope,
Locale: a.Settings.Defaults.Locale, Locale: a.Settings.Defaults.Locale,
ViewMode: a.Settings.Defaults.ViewMode, DefaultShareDurationTime: a.Settings.Defaults.DefaultShareDurationTime,
SingleClick: a.Settings.Defaults.SingleClick, DefaultShareDurationUnit: a.Settings.Defaults.DefaultShareDurationUnit,
Sorting: a.Settings.Defaults.Sorting, ViewMode: a.Settings.Defaults.ViewMode,
Perm: a.Settings.Defaults.Perm, SingleClick: a.Settings.Defaults.SingleClick,
Commands: a.Settings.Defaults.Commands, Sorting: a.Settings.Defaults.Sorting,
HideDotfiles: a.Settings.Defaults.HideDotfiles, Perm: a.Settings.Defaults.Perm,
Commands: a.Settings.Defaults.Commands,
HideDotfiles: a.Settings.Defaults.HideDotfiles,
} }
u = a.GetUser(d) u = a.GetUser(d)
@ -216,22 +219,26 @@ func (a *HookAuth) GetUser(d *users.User) *users.User {
Share: isAdmin || a.Fields.GetBoolean("user.perm.share", d.Perm.Share), Share: isAdmin || a.Fields.GetBoolean("user.perm.share", d.Perm.Share),
Download: isAdmin || a.Fields.GetBoolean("user.perm.download", d.Perm.Download), Download: isAdmin || a.Fields.GetBoolean("user.perm.download", d.Perm.Download),
} }
defaultShareDurationTime, _ := strconv.Atoi(
a.Fields.GetString("user.defaultShareDurationTime", strconv.Itoa(d.DefaultShareDurationTime)))
user := users.User{ user := users.User{
ID: d.ID, ID: d.ID,
Username: d.Username, Username: d.Username,
Password: d.Password, Password: d.Password,
Scope: a.Fields.GetString("user.scope", d.Scope), Scope: a.Fields.GetString("user.scope", d.Scope),
Locale: a.Fields.GetString("user.locale", d.Locale), Locale: a.Fields.GetString("user.locale", d.Locale),
ViewMode: users.ViewMode(a.Fields.GetString("user.viewMode", string(d.ViewMode))), DefaultShareDurationTime: defaultShareDurationTime,
SingleClick: a.Fields.GetBoolean("user.singleClick", d.SingleClick), DefaultShareDurationUnit: a.Fields.GetString("user.defaultShareDurationUnit", d.DefaultShareDurationUnit),
LockPassword: true,
ViewMode: users.ViewMode(a.Fields.GetString("user.viewMode", string(d.ViewMode))),
SingleClick: a.Fields.GetBoolean("user.singleClick", d.SingleClick),
Perm: perms,
Commands: a.Fields.GetArray("user.commands", d.Commands),
Sorting: files.Sorting{ Sorting: files.Sorting{
Asc: a.Fields.GetBoolean("user.sorting.asc", d.Sorting.Asc), Asc: a.Fields.GetBoolean("user.sorting.asc", d.Sorting.Asc),
By: a.Fields.GetString("user.sorting.by", d.Sorting.By), By: a.Fields.GetString("user.sorting.by", d.Sorting.By),
}, },
Commands: a.Fields.GetArray("user.commands", d.Commands),
HideDotfiles: a.Fields.GetBoolean("user.hideDotfiles", d.HideDotfiles), HideDotfiles: a.Fields.GetBoolean("user.hideDotfiles", d.HideDotfiles),
Perm: perms,
LockPassword: true,
} }
return &user return &user

View File

@ -318,9 +318,11 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
CreateUserDir: false, CreateUserDir: false,
UserHomeBasePath: settings.DefaultUsersHomeBasePath, UserHomeBasePath: settings.DefaultUsersHomeBasePath,
Defaults: settings.UserDefaults{ Defaults: settings.UserDefaults{
Scope: ".", Scope: ".",
Locale: "en", Locale: "en",
SingleClick: false, DefaultShareDurationTime: -1,
DefaultShareDurationUnit: "hours",
SingleClick: false,
Perm: users.Permissions{ Perm: users.Permissions{
Admin: false, Admin: false,
Execute: true, Execute: true,

View File

@ -27,14 +27,16 @@ var usersCmd = &cobra.Command{
func printUsers(usrs []*users.User) { func printUsers(usrs []*users.User) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) //nolint:gomnd w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) //nolint:gomnd
fmt.Fprintln(w, "ID\tUsername\tScope\tLocale\tV. Mode\tS.Click\tAdmin\tExecute\tCreate\tRename\tModify\tDelete\tShare\tDownload\tPwd Lock") fmt.Fprintln(w, "ID\tUsername\tScope\tLocale\tDefShare\tV. Mode\tS.Click\tAdmin\tExecute\tCreate\tRename\tModify\tDelete\tShare\tDownload\tPwd Lock")
for _, u := range usrs { for _, u := range usrs {
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t\n", fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%d%s\t%s\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t%t\t\n",
u.ID, u.ID,
u.Username, u.Username,
u.Scope, u.Scope,
u.Locale, u.Locale,
u.DefaultShareDurationTime,
u.DefaultShareDurationUnit,
u.ViewMode, u.ViewMode,
u.SingleClick, u.SingleClick,
u.Perm.Admin, u.Perm.Admin,
@ -75,6 +77,8 @@ func addUserFlags(flags *pflag.FlagSet) {
flags.StringSlice("commands", nil, "a list of the commands a user can execute") flags.StringSlice("commands", nil, "a list of the commands a user can execute")
flags.String("scope", ".", "scope for users") flags.String("scope", ".", "scope for users")
flags.String("locale", "en", "locale for users") flags.String("locale", "en", "locale for users")
flags.Int("defaultShareDurationTime", -1, "default share duration time for users (-1 disables it and 0 makes it permanent)")
flags.String("defaultShareDurationUnit", "hours", "default share duration unit for users")
flags.String("viewMode", string(users.ListViewMode), "view mode for users") flags.String("viewMode", string(users.ListViewMode), "view mode for users")
flags.Bool("singleClick", false, "use single clicks only") flags.Bool("singleClick", false, "use single clicks only")
} }
@ -95,6 +99,10 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all
defaults.Scope = mustGetString(flags, flag.Name) defaults.Scope = mustGetString(flags, flag.Name)
case "locale": case "locale":
defaults.Locale = mustGetString(flags, flag.Name) defaults.Locale = mustGetString(flags, flag.Name)
case "defaultShareDurationTime":
defaults.DefaultShareDurationTime = mustGetInt(flags, flag.Name)
case "defaultShareDurationUnit":
defaults.DefaultShareDurationUnit = mustGetString(flags, flag.Name)
case "viewMode": case "viewMode":
defaults.ViewMode = getViewMode(flags) defaults.ViewMode = getViewMode(flags)
case "singleClick": case "singleClick":

View File

@ -41,17 +41,21 @@ options you want to change.`,
checkErr(err) checkErr(err)
defaults := settings.UserDefaults{ defaults := settings.UserDefaults{
Scope: user.Scope, Scope: user.Scope,
Locale: user.Locale, Locale: user.Locale,
ViewMode: user.ViewMode, DefaultShareDurationTime: user.DefaultShareDurationTime,
SingleClick: user.SingleClick, DefaultShareDurationUnit: user.DefaultShareDurationUnit,
Perm: user.Perm, ViewMode: user.ViewMode,
Sorting: user.Sorting, SingleClick: user.SingleClick,
Commands: user.Commands, Perm: user.Perm,
Sorting: user.Sorting,
Commands: user.Commands,
} }
getUserDefaults(flags, &defaults, false) getUserDefaults(flags, &defaults, false)
user.Scope = defaults.Scope user.Scope = defaults.Scope
user.Locale = defaults.Locale user.Locale = defaults.Locale
user.DefaultShareDurationTime = defaults.DefaultShareDurationTime
user.DefaultShareDurationUnit = defaults.DefaultShareDurationUnit
user.ViewMode = defaults.ViewMode user.ViewMode = defaults.ViewMode
user.SingleClick = defaults.SingleClick user.SingleClick = defaults.SingleClick
user.Perm = defaults.Perm user.Perm = defaults.Perm

View File

@ -43,6 +43,12 @@ func mustGetUint(flags *pflag.FlagSet, flag string) uint {
return b return b
} }
func mustGetInt(flags *pflag.FlagSet, flag string) int {
b, err := flags.GetInt(flag)
checkErr(err)
return b
}
func generateKey() []byte { func generateKey() []byte {
k, err := settings.GenerateKey() k, err := settings.GenerateKey()
checkErr(err) checkErr(err)

View File

@ -24,6 +24,7 @@
</td> </td>
<td class="small"> <td class="small">
<button <button
ref="copyShare"
class="action copy-clipboard" class="action copy-clipboard"
:data-clipboard-text="buildLink(link)" :data-clipboard-text="buildLink(link)"
:aria-label="$t('buttons.copyToClipboard')" :aria-label="$t('buttons.copyToClipboard')"
@ -34,6 +35,7 @@
</td> </td>
<td class="small" v-if="hasDownloadLink()"> <td class="small" v-if="hasDownloadLink()">
<button <button
ref="copyDownload"
class="action copy-clipboard" class="action copy-clipboard"
:data-clipboard-text="buildDownloadLink(link)" :data-clipboard-text="buildDownloadLink(link)"
:aria-label="$t('buttons.copyDownloadLinkToClipboard')" :aria-label="$t('buttons.copyDownloadLinkToClipboard')"
@ -144,7 +146,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(["req", "selected", "selectedCount"]), ...mapState(["req", "selected", "selectedCount", "user"]),
...mapGetters(["isListing"]), ...mapGetters(["isListing"]),
url() { url() {
if (!this.isListing) { if (!this.isListing) {
@ -165,8 +167,23 @@ export default {
this.links = links; this.links = links;
this.sort(); this.sort();
if (this.links.length == 0) { if (this.links.length === 0) {
this.listing = false; this.listing = false;
// If enabled and this is the first share, automate the creation of the link and copy it to the clipboard.
if (this.user.defaultShareDurationTime !== -1) {
this.time = this.user.defaultShareDurationTime.toString();
this.unit = this.user.defaultShareDurationUnit;
await this.submit();
this.$nextTick(() => {
console.log(this.$refs)
if (this.$refs.hasOwnProperty("copyDownload") && this.$refs.copyDownload.length > 0) {
this.$refs.copyDownload[0].click();
} else if (this.$refs.hasOwnProperty("copyShare") && this.$refs.copyShare.length > 0) {
this.$refs.copyShare[0].click();
}
});
}
} }
} catch (e) { } catch (e) {
this.$showError(e); this.$showError(e);
@ -183,7 +200,7 @@ export default {
}, },
methods: { methods: {
submit: async function () { submit: async function () {
let isPermanent = !this.time || this.time == 0; let isPermanent = !this.time || this.time === "0";
try { try {
let res = null; let res = null;

View File

@ -298,15 +298,15 @@ body.rtl .card .card-title>*:first-child {
opacity: 1; opacity: 1;
} }
.card#share .input-group { .input-group {
display: flex; display: flex;
} }
.card#share .input-group * { .input-group * {
border: none; border: none;
} }
.card#share .input-group input { .input-group input {
flex: 1; flex: 1;
} }

View File

@ -199,6 +199,8 @@
"createUserHomeDirectory": "Create user home directory", "createUserHomeDirectory": "Create user home directory",
"customStylesheet": "Custom Stylesheet", "customStylesheet": "Custom Stylesheet",
"defaultUserDescription": "These are the default settings for new users.", "defaultUserDescription": "These are the default settings for new users.",
"defaultShareDuration": "Default share duration",
"defaultShareDurationExplanation": "The default share duration to automatically copy direct download links to the clipboard when creating a share. Set to 0 for permanent shares and -1 to disable the feature and choose a duration manually.",
"disableExternalLinks": "Disable external links (except documentation)", "disableExternalLinks": "Disable external links (except documentation)",
"disableUsedDiskPercentage": "Disable used disk percentage graph", "disableUsedDiskPercentage": "Disable used disk percentage graph",
"documentation": "documentation", "documentation": "documentation",

View File

@ -24,6 +24,23 @@
class="input input--block" class="input input--block"
:locale.sync="locale" :locale.sync="locale"
></languages> ></languages>
<h3>{{ $t("settings.defaultShareDuration") }}</h3>
<p>{{ $t("settings.defaultShareDurationExplanation") }}</p>
<div class="input-group input">
<input
v-focus
type="number"
max="2147483647"
min="-1"
v-model.trim="defaultShareDurationTime"
/>
<select class="right" v-model="defaultShareDurationUnit" :aria-label="$t('time.unit')">
<option value="seconds">{{ $t("time.seconds") }}</option>
<option value="minutes">{{ $t("time.minutes") }}</option>
<option value="hours">{{ $t("time.hours") }}</option>
<option value="days">{{ $t("time.days") }}</option>
</select>
</div>
</div> </div>
<div class="card-action"> <div class="card-action">
@ -90,6 +107,8 @@ export default {
singleClick: false, singleClick: false,
dateFormat: false, dateFormat: false,
locale: "", locale: "",
defaultShareDurationTime: -1,
defaultShareDurationUnit: "hours",
}; };
}, },
computed: { computed: {
@ -114,6 +133,8 @@ export default {
this.hideDotfiles = this.user.hideDotfiles; this.hideDotfiles = this.user.hideDotfiles;
this.singleClick = this.user.singleClick; this.singleClick = this.user.singleClick;
this.dateFormat = this.user.dateFormat; this.dateFormat = this.user.dateFormat;
this.defaultShareDurationTime = this.user.defaultShareDurationTime;
this.defaultShareDurationUnit = this.user.defaultShareDurationUnit;
}, },
methods: { methods: {
...mapMutations(["updateUser", "setLoading"]), ...mapMutations(["updateUser", "setLoading"]),
@ -143,6 +164,8 @@ export default {
hideDotfiles: this.hideDotfiles, hideDotfiles: this.hideDotfiles,
singleClick: this.singleClick, singleClick: this.singleClick,
dateFormat: this.dateFormat, dateFormat: this.dateFormat,
defaultShareDurationTime: parseInt(this.defaultShareDurationTime),
defaultShareDurationUnit: this.defaultShareDurationUnit,
}; };
const shouldReload = const shouldReload =
rtlLanguages.includes(data.locale) !== rtlLanguages.includes(data.locale) !==
@ -152,6 +175,8 @@ export default {
"hideDotfiles", "hideDotfiles",
"singleClick", "singleClick",
"dateFormat", "dateFormat",
"defaultShareDurationTime",
"defaultShareDurationUnit",
]); ]);
this.updateUser(data); this.updateUser(data);
if (shouldReload) { if (shouldReload) {

View File

@ -20,15 +20,17 @@ const (
) )
type userInfo struct { type userInfo struct {
ID uint `json:"id"` ID uint `json:"id"`
Locale string `json:"locale"` Locale string `json:"locale"`
ViewMode users.ViewMode `json:"viewMode"` DefaultShareDurationTime int `json:"defaultShareDurationTime"`
SingleClick bool `json:"singleClick"` DefaultShareDurationUnit string `json:"defaultShareDurationUnit"`
Perm users.Permissions `json:"perm"` ViewMode users.ViewMode `json:"viewMode"`
Commands []string `json:"commands"` SingleClick bool `json:"singleClick"`
LockPassword bool `json:"lockPassword"` Perm users.Permissions `json:"perm"`
HideDotfiles bool `json:"hideDotfiles"` Commands []string `json:"commands"`
DateFormat bool `json:"dateFormat"` LockPassword bool `json:"lockPassword"`
HideDotfiles bool `json:"hideDotfiles"`
DateFormat bool `json:"dateFormat"`
} }
type authToken struct { type authToken struct {
@ -184,15 +186,17 @@ func renewHandler(tokenExpireTime time.Duration) handleFunc {
func printToken(w http.ResponseWriter, _ *http.Request, d *data, user *users.User, tokenExpirationTime time.Duration) (int, error) { func printToken(w http.ResponseWriter, _ *http.Request, d *data, user *users.User, tokenExpirationTime time.Duration) (int, error) {
claims := &authToken{ claims := &authToken{
User: userInfo{ User: userInfo{
ID: user.ID, ID: user.ID,
Locale: user.Locale, Locale: user.Locale,
ViewMode: user.ViewMode, DefaultShareDurationTime: user.DefaultShareDurationTime,
SingleClick: user.SingleClick, DefaultShareDurationUnit: user.DefaultShareDurationUnit,
Perm: user.Perm, ViewMode: user.ViewMode,
LockPassword: user.LockPassword, SingleClick: user.SingleClick,
Commands: user.Commands, Perm: user.Perm,
HideDotfiles: user.HideDotfiles, LockPassword: user.LockPassword,
DateFormat: user.DateFormat, Commands: user.Commands,
HideDotfiles: user.HideDotfiles,
DateFormat: user.DateFormat,
}, },
RegisteredClaims: jwt.RegisteredClaims{ RegisteredClaims: jwt.RegisteredClaims{
IssuedAt: jwt.NewNumericDate(time.Now()), IssuedAt: jwt.NewNumericDate(time.Now()),

View File

@ -8,21 +8,25 @@ import (
// UserDefaults is a type that holds the default values // UserDefaults is a type that holds the default values
// for some fields on User. // for some fields on User.
type UserDefaults struct { type UserDefaults struct {
Scope string `json:"scope"` Scope string `json:"scope"`
Locale string `json:"locale"` Locale string `json:"locale"`
ViewMode users.ViewMode `json:"viewMode"` DefaultShareDurationTime int `json:"defaultShareDurationTime"`
SingleClick bool `json:"singleClick"` DefaultShareDurationUnit string `json:"defaultShareDurationUnit"`
Sorting files.Sorting `json:"sorting"` ViewMode users.ViewMode `json:"viewMode"`
Perm users.Permissions `json:"perm"` SingleClick bool `json:"singleClick"`
Commands []string `json:"commands"` Sorting files.Sorting `json:"sorting"`
HideDotfiles bool `json:"hideDotfiles"` Perm users.Permissions `json:"perm"`
DateFormat bool `json:"dateFormat"` Commands []string `json:"commands"`
HideDotfiles bool `json:"hideDotfiles"`
DateFormat bool `json:"dateFormat"`
} }
// Apply applies the default options to a user. // Apply applies the default options to a user.
func (d *UserDefaults) Apply(u *users.User) { func (d *UserDefaults) Apply(u *users.User) {
u.Scope = d.Scope u.Scope = d.Scope
u.Locale = d.Locale u.Locale = d.Locale
u.DefaultShareDurationTime = d.DefaultShareDurationTime
u.DefaultShareDurationUnit = d.DefaultShareDurationUnit
u.ViewMode = d.ViewMode u.ViewMode = d.ViewMode
u.SingleClick = d.SingleClick u.SingleClick = d.SingleClick
u.Perm = d.Perm u.Perm = d.Perm

View File

@ -121,10 +121,12 @@ func importConf(db *storm.DB, path string, sto *storage.Storage) error {
Key: key, Key: key,
Signup: false, Signup: false,
Defaults: settings.UserDefaults{ Defaults: settings.UserDefaults{
Scope: cfg.Defaults.Scope, Scope: cfg.Defaults.Scope,
Commands: cfg.Defaults.Commands, Commands: cfg.Defaults.Commands,
ViewMode: users.ViewMode(cfg.Defaults.ViewMode), ViewMode: users.ViewMode(cfg.Defaults.ViewMode),
Locale: cfg.Defaults.Locale, Locale: cfg.Defaults.Locale,
DefaultShareDurationTime: -1,
DefaultShareDurationUnit: "hours",
Perm: users.Permissions{ Perm: users.Permissions{
Admin: false, Admin: false,
Execute: cfg.Defaults.AllowCommands, Execute: cfg.Defaults.AllowCommands,

View File

@ -3,7 +3,6 @@ package importer
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/asdine/storm/v3" "github.com/asdine/storm/v3"
bolt "go.etcd.io/bbolt" bolt "go.etcd.io/bbolt"
@ -56,14 +55,16 @@ func convertUsersToNew(old []*oldUser) ([]*users.User, error) {
for _, oldUser := range old { for _, oldUser := range old {
user := &users.User{ user := &users.User{
Username: oldUser.Username, Username: oldUser.Username,
Password: oldUser.Password, Password: oldUser.Password,
Scope: oldUser.Scope, Scope: oldUser.Scope,
Locale: oldUser.Locale, Locale: oldUser.Locale,
LockPassword: oldUser.LockPassword, DefaultShareDurationTime: -1,
ViewMode: users.ViewMode(oldUser.ViewMode), DefaultShareDurationUnit: "hours",
Commands: oldUser.Commands, LockPassword: oldUser.LockPassword,
Rules: []rules.Rule{}, ViewMode: users.ViewMode(oldUser.ViewMode),
Commands: oldUser.Commands,
Rules: []rules.Rule{},
Perm: users.Permissions{ Perm: users.Permissions{
Admin: oldUser.Admin, Admin: oldUser.Admin,
Execute: oldUser.AllowCommands, Execute: oldUser.AllowCommands,

View File

@ -21,21 +21,23 @@ const (
// User describes a user. // User describes a user.
type User struct { type User struct {
ID uint `storm:"id,increment" json:"id"` ID uint `storm:"id,increment" json:"id"`
Username string `storm:"unique" json:"username"` Username string `storm:"unique" json:"username"`
Password string `json:"password"` Password string `json:"password"`
Scope string `json:"scope"` Scope string `json:"scope"`
Locale string `json:"locale"` Locale string `json:"locale"`
LockPassword bool `json:"lockPassword"` DefaultShareDurationTime int `json:"defaultShareDurationTime"`
ViewMode ViewMode `json:"viewMode"` DefaultShareDurationUnit string `json:"defaultShareDurationUnit"`
SingleClick bool `json:"singleClick"` LockPassword bool `json:"lockPassword"`
Perm Permissions `json:"perm"` ViewMode ViewMode `json:"viewMode"`
Commands []string `json:"commands"` SingleClick bool `json:"singleClick"`
Sorting files.Sorting `json:"sorting"` Perm Permissions `json:"perm"`
Fs afero.Fs `json:"-" yaml:"-"` Commands []string `json:"commands"`
Rules []rules.Rule `json:"rules"` Sorting files.Sorting `json:"sorting"`
HideDotfiles bool `json:"hideDotfiles"` Fs afero.Fs `json:"-" yaml:"-"`
DateFormat bool `json:"dateFormat"` Rules []rules.Rule `json:"rules"`
HideDotfiles bool `json:"hideDotfiles"`
DateFormat bool `json:"dateFormat"`
} }
// GetRules implements rules.Provider. // GetRules implements rules.Provider.