mirror of
https://github.com/abhinavxd/libredesk.git
synced 2026-09-24 19:32:10 +00:00
- show thumbnail image in widget thread instead of the entire image
- update file imports to use shared-ui utils and remove redundant file.js - Implement SignedURLStore interface for fs store
This commit is contained in:
+1
-1
@@ -42,7 +42,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { formatBytes } from '../../../../utils/file.js'
|
||||
import { formatBytes } from '@shared-ui/utils/file'
|
||||
import { X, Paperclip as PaperclipIcon } from 'lucide-vue-next'
|
||||
import { DotLoader } from '@shared-ui/components/ui/loader'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@shared-ui/components/ui/tooltip'
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { formatBytes } from '../../../../utils/file.js'
|
||||
import { formatBytes } from '@shared-ui/utils/file'
|
||||
import { Download } from 'lucide-vue-next';
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { formatBytes, getThumbFilepath } from '../../../../utils/file.js'
|
||||
import { formatBytes, getThumbFilepath } from '@shared-ui/utils/file'
|
||||
import { Download } from 'lucide-vue-next';
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
export function formatBytes(bytes) {
|
||||
if (bytes < 1024 * 1024) {
|
||||
return (bytes / 1024).toFixed(2) + ' KB'
|
||||
} else {
|
||||
return (bytes / (1024 * 1024)).toFixed(2) + ' MB'
|
||||
}
|
||||
}
|
||||
|
||||
export function getThumbFilepath (filepath) {
|
||||
const urlParts = filepath.split('/')
|
||||
const filename = urlParts.pop()
|
||||
return `/uploads/thumb_${filename}`
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
<!-- Image preview -->
|
||||
<div v-if="isImage(attachment)" class="relative">
|
||||
<img
|
||||
:src="attachment.url"
|
||||
:src="getThumbnailUrl(attachment)"
|
||||
:alt="attachment.name"
|
||||
class="max-w-48 max-h-32 rounded-lg object-cover"
|
||||
@click="openImage(attachment.url)"
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
<script setup>
|
||||
import { File } from 'lucide-vue-next';
|
||||
import { formatBytes } from '@shared-ui/utils/file';
|
||||
import { formatBytes, getThumbFilepath } from '@shared-ui/utils/file';
|
||||
defineProps({
|
||||
attachments: {
|
||||
type: Array,
|
||||
@@ -47,6 +47,11 @@ const isImage = (attachment) => {
|
||||
return attachment.content_type && attachment.content_type.startsWith('image/')
|
||||
}
|
||||
|
||||
const getThumbnailUrl = (attachment) => {
|
||||
if (!isImage(attachment)) return attachment.url
|
||||
return getThumbFilepath(attachment.url)
|
||||
}
|
||||
|
||||
const openImage = (url) => {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
@@ -5,3 +5,9 @@ export function formatBytes (bytes) {
|
||||
return (bytes / (1024 * 1024)).toFixed(2) + ' MB'
|
||||
}
|
||||
}
|
||||
|
||||
export function getThumbFilepath (filepath) {
|
||||
const urlParts = filepath.split('/')
|
||||
const filename = urlParts.pop()
|
||||
return `/uploads/thumb_${filename}`
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/abhinavxd/libredesk/internal/dbutil"
|
||||
@@ -266,7 +267,9 @@ func (m *Manager) VerifySignature(r *fastglue.Request) error {
|
||||
|
||||
// Check if store supports signature verification
|
||||
if signedStore, ok := m.store.(SignedURLStore); ok {
|
||||
if !signedStore.VerifySignature(uuid.(string), signature, expiresAt, []byte(m.secret)) {
|
||||
// Strip thumb_ prefix for signature verification to match the base UUID
|
||||
verificationName := strings.TrimPrefix(uuid.(string), "thumb_")
|
||||
if !signedStore.VerifySignature(verificationName, signature, expiresAt, []byte(m.secret)) {
|
||||
return fmt.Errorf("signature verification failed")
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/abhinavxd/libredesk/internal/media"
|
||||
)
|
||||
@@ -73,6 +80,58 @@ func (c *Client) Name() string {
|
||||
return "fs"
|
||||
}
|
||||
|
||||
// GetSignedURL generates a signed URL for the file with expiration.
|
||||
// This implements the SignedURLStore interface for secure public access.
|
||||
func (c *Client) GetSignedURL(name string, expiresAt time.Time, secret []byte) string {
|
||||
// Generate base URL
|
||||
baseURL := c.GetURL(name)
|
||||
|
||||
// Create the signature payload: name + expires timestamp
|
||||
expires := expiresAt.Unix()
|
||||
payload := name + strconv.FormatInt(expires, 10)
|
||||
|
||||
// Generate HMAC-SHA256 signature
|
||||
h := hmac.New(sha256.New, secret)
|
||||
h.Write([]byte(payload))
|
||||
signature := base64.URLEncoding.EncodeToString(h.Sum(nil))
|
||||
|
||||
// Parse base URL and add query parameters
|
||||
u, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
// Fallback to base URL if parsing fails
|
||||
return baseURL
|
||||
}
|
||||
|
||||
// Add signature and expires parameters
|
||||
query := u.Query()
|
||||
query.Set("signature", signature)
|
||||
query.Set("expires", strconv.FormatInt(expires, 10))
|
||||
u.RawQuery = query.Encode()
|
||||
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// VerifySignature verifies that a signature is valid for the given parameters.
|
||||
// This implements the SignedURLStore interface for secure public access.
|
||||
func (c *Client) VerifySignature(name, signature string, expiresAt time.Time, secret []byte) bool {
|
||||
// Check if URL has expired
|
||||
if time.Now().After(expiresAt) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Recreate the signature payload: name + expires timestamp
|
||||
expires := expiresAt.Unix()
|
||||
payload := name + strconv.FormatInt(expires, 10)
|
||||
|
||||
// Generate expected HMAC-SHA256 signature
|
||||
h := hmac.New(sha256.New, secret)
|
||||
h.Write([]byte(payload))
|
||||
expectedSignature := base64.URLEncoding.EncodeToString(h.Sum(nil))
|
||||
|
||||
// Use constant-time comparison to prevent timing attacks
|
||||
return subtle.ConstantTimeCompare([]byte(signature), []byte(expectedSignature)) == 1
|
||||
}
|
||||
|
||||
// getDir returns the current working directory path if no directory is specified,
|
||||
// else returns the directory path specified itself.
|
||||
func getDir(dir string) string {
|
||||
|
||||
Reference in New Issue
Block a user