mirror of
https://github.com/gtsteffaniak/filebrowser.git
synced 2026-11-05 09:10:14 -05:00
Compare commits
16
Commits
main
...
v1.1.5-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
010de838b1 | ||
|
|
07acd2f7bf | ||
|
|
d049470d29 | ||
|
|
180cc40567 | ||
|
|
40a4b51130 | ||
|
|
68fb573059 | ||
|
|
5747c40dca | ||
|
|
598bd366db | ||
|
|
e3be3300dc | ||
|
|
416aa75446 | ||
|
|
cfb3b9e869 | ||
|
|
166f1bf232 | ||
|
|
451f9ac69f | ||
|
|
b2fa1780ed | ||
|
|
9255bc174b | ||
|
|
a3a074d7b6 |
No files matched your search
@@ -59,7 +59,7 @@ jobs:
|
||||
images: |
|
||||
gtstef/filebrowser
|
||||
ghcr.io/gtsteffaniak/filebrowser
|
||||
- name: Modify tags (strip 'v' and add 'beta'/'stable' if needed)
|
||||
- name: Modify tags (strip 'v' and add version tags)
|
||||
id: modify-json
|
||||
run: |
|
||||
JSON='${{ steps.meta.outputs.json }}'
|
||||
@@ -68,13 +68,31 @@ jobs:
|
||||
sub(":v"; ":") # Strip the 'v'
|
||||
| select(test(":latest") | not)
|
||||
) |
|
||||
if any(.tags[]; test("beta")) then
|
||||
# Find all version tags with suffix (X.Y.Z-suffix) and add major.minor and major tags
|
||||
.tags += [
|
||||
.tags[]
|
||||
| select(test(":[0-9]+\\.[0-9]+\\.[0-9]+-"))
|
||||
| split(":")
|
||||
| .[-1] as $version
|
||||
| ($version | split("-")) as $version_split
|
||||
| ($version_split[0] | split(".")) as $version_parts
|
||||
| ($version_split[1:] | join("-")) as $suffix
|
||||
| ($version_parts | "\(.[0]).\(.[1])") as $major_minor
|
||||
| ($version_parts[0]) as $major
|
||||
| "gtstef/filebrowser:\($major_minor)-\($suffix)",
|
||||
"ghcr.io/gtsteffaniak/filebrowser:\($major_minor)-\($suffix)",
|
||||
"gtstef/filebrowser:\($major)-\($suffix)",
|
||||
"ghcr.io/gtsteffaniak/filebrowser:\($major)-\($suffix)"
|
||||
] |
|
||||
# Add suffix-only tags for beta (only if tag ends with -beta)
|
||||
if any(.tags[]; split(":")[-1] | endswith("-beta")) then
|
||||
.tags += [
|
||||
"gtstef/filebrowser:beta",
|
||||
"ghcr.io/gtsteffaniak/filebrowser:beta"
|
||||
]
|
||||
else . end |
|
||||
if any(.tags[]; test("stable")) then
|
||||
# Add suffix-only tags for stable (and latest) (only if tag ends with -stable)
|
||||
if any(.tags[]; split(":")[-1] | endswith("-stable")) then
|
||||
.tags += [
|
||||
"gtstef/filebrowser:stable",
|
||||
"ghcr.io/gtsteffaniak/filebrowser:stable",
|
||||
@@ -136,13 +154,31 @@ jobs:
|
||||
| select(test(":latest") | not) # Remove the "latest" tag
|
||||
| . + "-slim" # Append "-slim" to the versioned tag
|
||||
) |
|
||||
if any(.tags[]; test("beta")) then
|
||||
# Find all version tags with suffix (X.Y.Z-suffix-slim) and add major.minor and major tags
|
||||
.tags += [
|
||||
.tags[]
|
||||
| select(test(":[0-9]+\\.[0-9]+\\.[0-9]+-"))
|
||||
| split(":")
|
||||
| .[-1] as $version
|
||||
| ($version | split("-")) as $version_split
|
||||
| ($version_split[0] | split(".")) as $version_parts
|
||||
| ($version_split[1:] | join("-")) as $suffix
|
||||
| ($version_parts | "\(.[0]).\(.[1])") as $major_minor
|
||||
| ($version_parts[0]) as $major
|
||||
| "gtstef/filebrowser:\($major_minor)-\($suffix)",
|
||||
"ghcr.io/gtsteffaniak/filebrowser:\($major_minor)-\($suffix)",
|
||||
"gtstef/filebrowser:\($major)-\($suffix)",
|
||||
"ghcr.io/gtsteffaniak/filebrowser:\($major)-\($suffix)"
|
||||
] |
|
||||
# Add suffix-only tags for beta-slim (only if tag ends with -beta-slim)
|
||||
if any(.tags[]; split(":")[-1] | endswith("-beta-slim")) then
|
||||
.tags += [
|
||||
"gtstef/filebrowser:beta-slim",
|
||||
"ghcr.io/gtsteffaniak/filebrowser:beta-slim"
|
||||
]
|
||||
else . end |
|
||||
if any(.tags[]; test("stable")) then
|
||||
# Add suffix-only tags for stable-slim (only if tag ends with -stable-slim)
|
||||
if any(.tags[]; split(":")[-1] | endswith("-stable-slim")) then
|
||||
.tags += [
|
||||
"gtstef/filebrowser:stable-slim",
|
||||
"ghcr.io/gtsteffaniak/filebrowser:stable-slim"
|
||||
|
||||
@@ -2,6 +2,19 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. For commit guidelines, please refer to [Standard Version](https://github.com/conventional-changelog/standard-version).
|
||||
|
||||
## v1.1.5-beta
|
||||
|
||||
**Notes**:
|
||||
- Major git container tag request #1756
|
||||
|
||||
**BugFixes**:
|
||||
- Sharing broken if source `disableIndexing: true` (#1742)
|
||||
- Password protected share permission issues (#1729) (#1606) (#1593)
|
||||
- Sidebar Sliding left (#1737)
|
||||
- file permissions not working (#1762)
|
||||
- handle Close() errors in archive creation (#1745)
|
||||
- Move custom CSS and user-selected theme styles to the end of the body (#1744)
|
||||
|
||||
## v1.1.4-beta
|
||||
|
||||
**New Features**:
|
||||
|
||||
+31
-5
@@ -1,6 +1,9 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
libError "errors"
|
||||
"fmt"
|
||||
@@ -333,8 +336,34 @@ func authenticateShareRequest(r *http.Request, l *share.Link) (int, error) {
|
||||
return 200, nil
|
||||
}
|
||||
|
||||
if r.URL.Query().Get("token") == l.Token {
|
||||
return 200, nil
|
||||
tokenParam := r.URL.Query().Get("token")
|
||||
if tokenParam != "" {
|
||||
// Verify the token signature if it's in the new signed format
|
||||
if strings.Contains(tokenParam, ".") {
|
||||
parts := strings.Split(tokenParam, ".")
|
||||
if len(parts) == 2 {
|
||||
payload := parts[0]
|
||||
signature := parts[1]
|
||||
|
||||
// Verify HMAC signature
|
||||
mac := hmac.New(sha256.New, []byte(config.Auth.Key))
|
||||
mac.Write([]byte(payload))
|
||||
expectedSignature := base64.URLEncoding.EncodeToString(mac.Sum(nil))
|
||||
|
||||
// Use constant-time comparison to prevent timing attacks
|
||||
if hmac.Equal([]byte(signature), []byte(expectedSignature)) {
|
||||
// Token signature is valid, now check if it matches stored token
|
||||
if tokenParam == l.Token {
|
||||
return 200, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Legacy token format (plain base64) - direct comparison
|
||||
if tokenParam == l.Token {
|
||||
return 200, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
password := r.Header.Get("X-SHARE-PASSWORD")
|
||||
@@ -342,9 +371,6 @@ func authenticateShareRequest(r *http.Request, l *share.Link) (int, error) {
|
||||
if err != nil {
|
||||
return http.StatusUnauthorized, err
|
||||
}
|
||||
if password == "" {
|
||||
return http.StatusUnauthorized, nil
|
||||
}
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(l.PasswordHash), []byte(password)); err != nil {
|
||||
if libError.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
|
||||
return http.StatusUnauthorized, nil
|
||||
|
||||
+40
-26
@@ -1,7 +1,9 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -62,8 +64,8 @@ func convertToFrontendShareResponse(r *http.Request, shares []*share.Link) ([]*S
|
||||
pathExists := utils.CheckPathExists(filepath.Join(sourceInfo.Path, s.Path))
|
||||
|
||||
s.CommonShare.HasPassword = s.HasPassword()
|
||||
s.DownloadURL = getShareURL(r, s.Hash, true)
|
||||
s.ShareURL = getShareURL(r, s.Hash, false)
|
||||
s.DownloadURL = getShareURL(r, s.Hash, true, s.Token)
|
||||
s.ShareURL = getShareURL(r, s.Hash, false, s.Token)
|
||||
|
||||
// Create response with source name (overrides the embedded Link's source field)
|
||||
responses = append(responses, &ShareResponse{
|
||||
@@ -283,11 +285,21 @@ func sharePostHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
stringHash := ""
|
||||
var token string
|
||||
if len(hash) > 0 {
|
||||
tokenBuffer := make([]byte, 24)
|
||||
if _, err = rand.Read(tokenBuffer); err != nil {
|
||||
// Generate a cryptographically secure token similar to JWT
|
||||
// Create a random payload
|
||||
payloadBuffer := make([]byte, 24)
|
||||
if _, err = rand.Read(payloadBuffer); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
token = base64.URLEncoding.EncodeToString(tokenBuffer)
|
||||
payload := base64.URLEncoding.EncodeToString(payloadBuffer)
|
||||
|
||||
// Sign the payload with HMAC-SHA256 using the same secret key as JWT tokens
|
||||
mac := hmac.New(sha256.New, []byte(config.Auth.Key))
|
||||
mac.Write([]byte(payload))
|
||||
signature := base64.URLEncoding.EncodeToString(mac.Sum(nil))
|
||||
|
||||
// Combine payload and signature: payload.signature (similar to JWT format)
|
||||
token = payload + "." + signature
|
||||
stringHash = string(hash)
|
||||
}
|
||||
if s != nil {
|
||||
@@ -352,17 +364,13 @@ func sharePostHandler(w http.ResponseWriter, r *http.Request, d *requestContext)
|
||||
if err != nil {
|
||||
return http.StatusForbidden, err
|
||||
}
|
||||
scopePath := utils.JoinPathAsUnix(userscope, body.Path)
|
||||
scopePath = utils.AddTrailingSlashIfNotExists(scopePath)
|
||||
body.Path = scopePath
|
||||
providedPath := body.Path
|
||||
body.Path = utils.JoinPathAsUnix(userscope, providedPath)
|
||||
body.Path = utils.AddTrailingSlashIfNotExists(body.Path)
|
||||
// validate path exists as file or folder
|
||||
_, exists := idx.GetReducedMetadata(body.Path, true) // true to check if it exists
|
||||
if !exists {
|
||||
// could be a file instead
|
||||
_, exists := idx.GetReducedMetadata(utils.GetParentDirectoryPath(body.Path), true)
|
||||
if !exists {
|
||||
return http.StatusForbidden, fmt.Errorf("path not found: %s", body.Path)
|
||||
}
|
||||
_, _, err = idx.GetRealPath(body.Path)
|
||||
if err != nil {
|
||||
return http.StatusForbidden, fmt.Errorf("path not found: %s", providedPath)
|
||||
}
|
||||
if body.ShareType == "upload" && !body.AllowCreate {
|
||||
body.AllowCreate = true
|
||||
@@ -510,8 +518,8 @@ func shareDirectDownloadHandler(w http.ResponseWriter, r *http.Request, d *reque
|
||||
response := DirectDownloadResponse{
|
||||
Status: "201",
|
||||
Hash: existing.Hash,
|
||||
DownloadURL: getShareURL(r, existing.Hash, true),
|
||||
ShareURL: getShareURL(r, existing.Hash, false),
|
||||
DownloadURL: getShareURL(r, existing.Hash, true, existing.Token),
|
||||
ShareURL: getShareURL(r, existing.Hash, false, existing.Token),
|
||||
}
|
||||
return renderJSON(w, r, response)
|
||||
}
|
||||
@@ -542,18 +550,23 @@ func shareDirectDownloadHandler(w http.ResponseWriter, r *http.Request, d *reque
|
||||
response := DirectDownloadResponse{
|
||||
Status: "200",
|
||||
Hash: secureHash,
|
||||
DownloadURL: getShareURL(r, secureHash, true),
|
||||
ShareURL: getShareURL(r, secureHash, false),
|
||||
DownloadURL: getShareURL(r, secureHash, true, shareLink.Token),
|
||||
ShareURL: getShareURL(r, secureHash, false, shareLink.Token),
|
||||
}
|
||||
|
||||
return renderJSON(w, r, response)
|
||||
}
|
||||
|
||||
func getShareURL(r *http.Request, hash string, isDirectDownload bool) string {
|
||||
func getShareURL(r *http.Request, hash string, isDirectDownload bool, token string) string {
|
||||
var shareURL string
|
||||
tokenParam := ""
|
||||
if token != "" && isDirectDownload {
|
||||
tokenParam = fmt.Sprintf("&token=%s", url.QueryEscape(token))
|
||||
}
|
||||
|
||||
if config.Server.ExternalUrl != "" {
|
||||
if isDirectDownload {
|
||||
shareURL = fmt.Sprintf("%s%spublic/api/raw?hash=%s", config.Server.ExternalUrl, config.Server.BaseURL, hash)
|
||||
shareURL = fmt.Sprintf("%s%spublic/api/raw?hash=%s%s", config.Server.ExternalUrl, config.Server.BaseURL, hash, tokenParam)
|
||||
} else {
|
||||
shareURL = fmt.Sprintf("%s%spublic/share/%s", config.Server.ExternalUrl, config.Server.BaseURL, hash)
|
||||
}
|
||||
@@ -576,7 +589,7 @@ func getShareURL(r *http.Request, hash string, isDirectDownload bool) string {
|
||||
scheme = getScheme(r)
|
||||
}
|
||||
if isDirectDownload {
|
||||
shareURL = fmt.Sprintf("%s://%s%spublic/api/raw?hash=%s", scheme, host, config.Server.BaseURL, hash)
|
||||
shareURL = fmt.Sprintf("%s://%s%spublic/api/raw?hash=%s%s", scheme, host, config.Server.BaseURL, hash, tokenParam)
|
||||
} else {
|
||||
shareURL = fmt.Sprintf("%s://%s%spublic/share/%s", scheme, host, config.Server.BaseURL, hash)
|
||||
}
|
||||
@@ -596,13 +609,14 @@ func getShareURL(r *http.Request, hash string, isDirectDownload bool) string {
|
||||
// @Router /public/api/shareinfo [get]
|
||||
func shareInfoHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
|
||||
hash := r.URL.Query().Get("hash")
|
||||
// Get the file link by hash
|
||||
commonShare, err := store.Share.GetCommonShareByHash(hash)
|
||||
// Get the file link by hash (need full Link to get Token)
|
||||
shareLink, err := store.Share.GetByHash(hash)
|
||||
if err != nil {
|
||||
return http.StatusNotFound, fmt.Errorf("share hash not found")
|
||||
}
|
||||
commonShare.DownloadURL = getShareURL(r, hash, true)
|
||||
commonShare.ShareURL = getShareURL(r, hash, false)
|
||||
commonShare := shareLink.CommonShare
|
||||
commonShare.DownloadURL = getShareURL(r, hash, true, shareLink.Token)
|
||||
commonShare.ShareURL = getShareURL(r, hash, false, shareLink.Token)
|
||||
return renderJSON(w, r, commonShare)
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
|
||||
staticURL := config.Server.BaseURL + "public/static"
|
||||
description := config.Frontend.Description
|
||||
title := config.Frontend.Name
|
||||
disableSidebar := false
|
||||
|
||||
// Use custom favicon if configured and validated, otherwise fall back to default
|
||||
favicon := staticURL + "/favicon"
|
||||
@@ -104,6 +105,9 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
|
||||
userSelectedTheme = theme.CssRaw
|
||||
}
|
||||
}
|
||||
if d.share.DisableSidebar {
|
||||
disableSidebar = true
|
||||
}
|
||||
}
|
||||
// Set login icon URL
|
||||
loginIcon := staticURL + "/loginIcon"
|
||||
@@ -148,6 +152,7 @@ func handleWithStaticData(w http.ResponseWriter, r *http.Request, d *requestCont
|
||||
"enableHeicConversion": config.Integrations.Media.Convert.ImagePreview[settings.HEICImagePreview] && settings.MediaEnabled(),
|
||||
"eventBasedThemes": !config.Frontend.Styling.DisableEventBasedThemes,
|
||||
"loginIcon": loginIcon,
|
||||
"disableSidebar": disableSidebar,
|
||||
}
|
||||
|
||||
// Marshal each variable to JSON strings for direct template usage
|
||||
|
||||
@@ -91,7 +91,6 @@ export function getPreviewURL(path,size="small") {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function post(
|
||||
hash,
|
||||
path,
|
||||
|
||||
@@ -188,7 +188,7 @@ export const getters = {
|
||||
return { dirs, files }
|
||||
},
|
||||
isSidebarVisible: () => {
|
||||
if (state.shareInfo?.disableSidebar) {
|
||||
if (globalVars.disableSidebar) {
|
||||
return false
|
||||
}
|
||||
const cv = getters.currentView()
|
||||
|
||||
@@ -204,6 +204,10 @@ export default {
|
||||
this.attemptedPasswordLogin = this.sharePassword !== "";
|
||||
// Reset password validation state on wrong password
|
||||
mutations.setShareData({ passwordValid: false });
|
||||
// Clear error for upload shares so upload interface can be shown once password is correct
|
||||
if (state.shareInfo?.shareType === "upload") {
|
||||
this.error = null;
|
||||
}
|
||||
this.showPasswordPrompt();
|
||||
} else {
|
||||
router.push({ name: "error" });
|
||||
@@ -234,46 +238,104 @@ export default {
|
||||
}
|
||||
shareInfo.hash = hash;
|
||||
mutations.setShareInfo(shareInfo);
|
||||
if (shareInfo.shareType == "upload") {
|
||||
return;
|
||||
}
|
||||
if (shareInfo.themeColor) {
|
||||
document.documentElement.style.setProperty("--primaryColor", shareInfo.themeColor);
|
||||
}
|
||||
if (shareInfo.hasPassword && this.sharePassword === "") {
|
||||
this.sharePassword = localStorage.getItem("sharepass:" + this.shareHash);
|
||||
if (this.sharePassword === null || this.sharePassword === "") {
|
||||
this.showPasswordPrompt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse share route
|
||||
|
||||
// Parse share route to get shareHash and shareSubPath
|
||||
let urlPath = getters.routePath('public/share')
|
||||
let parts = urlPath.split("/");
|
||||
this.shareHash = parts[1]
|
||||
this.shareSubPath = "/" + parts.slice(2).join("/");
|
||||
|
||||
// Handle password
|
||||
if (this.sharePassword === "") {
|
||||
this.sharePassword = localStorage.getItem("sharepass:" + this.shareHash);
|
||||
} else {
|
||||
|
||||
// Check for password requirement (applies to both regular and upload shares)
|
||||
if (shareInfo.hasPassword) {
|
||||
if (this.sharePassword === "") {
|
||||
this.sharePassword = localStorage.getItem("sharepass:" + this.shareHash);
|
||||
if (this.sharePassword === null || this.sharePassword === "") {
|
||||
this.showPasswordPrompt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Store password in localStorage
|
||||
localStorage.setItem("sharepass:" + this.shareHash, this.sharePassword);
|
||||
}
|
||||
|
||||
if (shareInfo.themeColor) {
|
||||
document.documentElement.style.setProperty("--primaryColor", shareInfo.themeColor);
|
||||
}
|
||||
|
||||
// Handle password (same for both regular and upload shares)
|
||||
if (this.sharePassword === null) {
|
||||
this.sharePassword = "";
|
||||
}
|
||||
|
||||
// For upload shares, validate password on startup and return early
|
||||
// Password validation happens via fetchPub call, which will throw 401 if incorrect
|
||||
// A 501 error means browsing is disabled (expected for upload shares) and indicates auth succeeded
|
||||
if (shareInfo.shareType == "upload") {
|
||||
// Initialize password validation state
|
||||
if (shareInfo.hasPassword) {
|
||||
mutations.setShareData({ passwordValid: false });
|
||||
try {
|
||||
await publicApi.fetchPub(this.shareSubPath, this.shareHash, this.sharePassword, false);
|
||||
// If we get here, password is valid (unlikely for upload shares, but handle it)
|
||||
mutations.setShareData({ passwordValid: true });
|
||||
this.error = null; // Clear any previous errors
|
||||
} catch (e) {
|
||||
// 501 means browsing is disabled for upload shares - this is expected and means auth succeeded
|
||||
if (e.status === 501) {
|
||||
// Password is valid, mark as validated
|
||||
mutations.setShareData({ passwordValid: true });
|
||||
this.error = null; // Clear any previous errors
|
||||
} else if (e.status === 401) {
|
||||
// Password is invalid, show prompt
|
||||
this.attemptedPasswordLogin = true;
|
||||
mutations.setShareData({ passwordValid: false });
|
||||
this.showPasswordPrompt();
|
||||
return;
|
||||
} else {
|
||||
// For other errors, re-throw to be handled by fetchData()
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No password required, mark as validated
|
||||
mutations.setShareData({ passwordValid: true });
|
||||
this.error = null; // Clear any previous errors
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// For regular shares, validate password on startup (similar to upload shares)
|
||||
if (shareInfo.hasPassword) {
|
||||
mutations.setShareData({ passwordValid: false });
|
||||
try {
|
||||
await publicApi.fetchPub(this.shareSubPath, this.shareHash, this.sharePassword, false);
|
||||
// Password is valid
|
||||
mutations.setShareData({ passwordValid: true });
|
||||
this.error = null; // Clear any previous errors
|
||||
} catch (e) {
|
||||
if (e.status === 401) {
|
||||
// Password is invalid, show prompt
|
||||
this.attemptedPasswordLogin = true;
|
||||
mutations.setShareData({ passwordValid: false });
|
||||
this.showPasswordPrompt();
|
||||
return;
|
||||
} else {
|
||||
// For other errors, re-throw to be handled by fetchData()
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No password required, mark as validated
|
||||
mutations.setShareData({ passwordValid: true });
|
||||
this.error = null; // Clear any previous errors
|
||||
}
|
||||
|
||||
mutations.resetSelected();
|
||||
mutations.setMultiple(false);
|
||||
|
||||
if (state.shareInfo?.singleFileShare) {
|
||||
mutations.setSidebarVisible(true);
|
||||
}
|
||||
// Initialize password validation state for password-protected shares
|
||||
if (state.shareInfo?.isPasswordProtected) {
|
||||
mutations.setShareData({ passwordValid: false });
|
||||
}
|
||||
// Fetch share data
|
||||
let file = await publicApi.fetchPub(this.shareSubPath, this.shareHash, this.sharePassword);
|
||||
file.hash = this.shareHash;
|
||||
|
||||
@@ -181,7 +181,8 @@
|
||||
</div>
|
||||
|
||||
<!-- Upload Share Target -->
|
||||
<div v-else class="upload-share-embed">
|
||||
<!-- Only show upload interface if password is validated (or no password required) -->
|
||||
<div v-else-if="!shareInfo.hasPassword || state.share?.passwordValid" class="upload-share-embed">
|
||||
<Upload :initialItems="null" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in new issue
Block a user