修复显示问题
This commit is contained in:
Binary file not shown.
@@ -37,6 +37,11 @@ type HTTPHandler struct {
|
|||||||
deps Dependencies
|
deps Dependencies
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
deviceIDCookieName = "filefast_device_id"
|
||||||
|
deviceTokenCookieName = "filefast_device_token"
|
||||||
|
)
|
||||||
|
|
||||||
func NewHTTPHandler(deps Dependencies) *HTTPHandler {
|
func NewHTTPHandler(deps Dependencies) *HTTPHandler {
|
||||||
return &HTTPHandler{deps: deps}
|
return &HTTPHandler{deps: deps}
|
||||||
}
|
}
|
||||||
@@ -531,8 +536,7 @@ func (h *HTTPHandler) requireAdmin() gin.HandlerFunc {
|
|||||||
|
|
||||||
func (h *HTTPHandler) requireDevice() gin.HandlerFunc {
|
func (h *HTTPHandler) requireDevice() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
deviceID := strings.TrimSpace(c.GetHeader("X-Device-ID"))
|
deviceID, token := deviceCredentialsFromRequest(c)
|
||||||
token := strings.TrimSpace(c.GetHeader("X-Device-Token"))
|
|
||||||
if deviceID == "" || token == "" {
|
if deviceID == "" || token == "" {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing device credentials"})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing device credentials"})
|
||||||
return
|
return
|
||||||
@@ -546,6 +550,27 @@ func (h *HTTPHandler) requireDevice() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deviceCredentialsFromRequest(c *gin.Context) (string, string) {
|
||||||
|
deviceID := strings.TrimSpace(c.GetHeader("X-Device-ID"))
|
||||||
|
token := strings.TrimSpace(c.GetHeader("X-Device-Token"))
|
||||||
|
if deviceID != "" && token != "" {
|
||||||
|
return deviceID, token
|
||||||
|
}
|
||||||
|
|
||||||
|
if deviceID == "" {
|
||||||
|
if value, err := c.Cookie(deviceIDCookieName); err == nil {
|
||||||
|
deviceID = strings.TrimSpace(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if token == "" {
|
||||||
|
if value, err := c.Cookie(deviceTokenCookieName); err == nil {
|
||||||
|
token = strings.TrimSpace(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deviceID, token
|
||||||
|
}
|
||||||
|
|
||||||
func (h *HTTPHandler) authenticatedDeviceID(c *gin.Context) string {
|
func (h *HTTPHandler) authenticatedDeviceID(c *gin.Context) string {
|
||||||
value, ok := c.Get("device_id")
|
value, ok := c.Get("device_id")
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -114,6 +114,26 @@ func TestTransferStatusUpdateRequiresParticipantOwnership(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProtectedRoutesAcceptDeviceCredentialsFromCookies(t *testing.T) {
|
||||||
|
router, _ := newTestRouter()
|
||||||
|
|
||||||
|
device := registerDevice(t, router, map[string]any{
|
||||||
|
"device_id": "cookie-device",
|
||||||
|
"name": "Cookie Device",
|
||||||
|
"type": "desktop",
|
||||||
|
})
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/devices/candidates?deviceId="+device.ID, nil)
|
||||||
|
req.AddCookie(&http.Cookie{Name: deviceIDCookieName, Value: device.ID})
|
||||||
|
req.AddCookie(&http.Cookie{Name: deviceTokenCookieName, Value: device.AuthToken})
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(resp, req)
|
||||||
|
|
||||||
|
if resp.Code != http.StatusOK {
|
||||||
|
t.Fatalf("expected cookie-authenticated request to succeed, got %d: %s", resp.Code, resp.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newTestRouter() (http.Handler, *store.MemoryStore) {
|
func newTestRouter() (http.Handler, *store.MemoryStore) {
|
||||||
memStore := store.NewMemoryStore(model.RuntimeConfig{})
|
memStore := store.NewMemoryStore(model.RuntimeConfig{})
|
||||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||||
|
|||||||
17
frontend/dist/assets/index-BhftK8R5.js
vendored
17
frontend/dist/assets/index-BhftK8R5.js
vendored
File diff suppressed because one or more lines are too long
1
frontend/dist/assets/index-CQ9sinZs.css
vendored
Normal file
1
frontend/dist/assets/index-CQ9sinZs.css
vendored
Normal file
File diff suppressed because one or more lines are too long
17
frontend/dist/assets/index-DE3lDjdM.js
vendored
Normal file
17
frontend/dist/assets/index-DE3lDjdM.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
frontend/dist/assets/index-jSzxC_eO.css
vendored
1
frontend/dist/assets/index-jSzxC_eO.css
vendored
File diff suppressed because one or more lines are too long
4
frontend/dist/index.html
vendored
4
frontend/dist/index.html
vendored
@@ -4,8 +4,8 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>AirShare Pro</title>
|
<title>AirShare Pro</title>
|
||||||
<script type="module" crossorigin src="/assets/index-BhftK8R5.js"></script>
|
<script type="module" crossorigin src="/assets/index-DE3lDjdM.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-jSzxC_eO.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-CQ9sinZs.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ let deviceSession = {
|
|||||||
token: '',
|
token: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEVICE_ID_COOKIE = 'filefast_device_id'
|
||||||
|
const DEVICE_TOKEN_COOKIE = 'filefast_device_token'
|
||||||
|
|
||||||
function buildDeviceHeaders() {
|
function buildDeviceHeaders() {
|
||||||
if (!deviceSession.deviceId || !deviceSession.token) {
|
if (!deviceSession.deviceId || !deviceSession.token) {
|
||||||
return {}
|
return {}
|
||||||
@@ -77,6 +80,8 @@ export function setDeviceSession(deviceId, token) {
|
|||||||
deviceId: deviceId || '',
|
deviceId: deviceId || '',
|
||||||
token: token || '',
|
token: token || '',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
syncDeviceSessionCookies(deviceSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearDeviceSession() {
|
export function clearDeviceSession() {
|
||||||
@@ -92,3 +97,21 @@ export function withBearerToken(token) {
|
|||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncDeviceSessionCookies(session) {
|
||||||
|
if (typeof document === 'undefined') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCookie(DEVICE_ID_COOKIE, session.deviceId)
|
||||||
|
writeCookie(DEVICE_TOKEN_COOKIE, session.token)
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeCookie(name, value) {
|
||||||
|
if (!value) {
|
||||||
|
document.cookie = `${name}=; Path=/; Max-Age=0; SameSite=Lax`
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
document.cookie = `${name}=${encodeURIComponent(value)}; Path=/; SameSite=Lax`
|
||||||
|
}
|
||||||
|
|||||||
@@ -72,10 +72,12 @@ function handleEnter() {
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<div class="pending-download-copy">
|
<div class="pending-download-copy">
|
||||||
<strong>{{ item.name }}</strong>
|
<strong :title="item.name">{{ item.name }}</strong>
|
||||||
<p>{{ item.size_label }} · {{ item.created_label }}</p>
|
<p>{{ item.size_label }} · {{ item.created_label }}</p>
|
||||||
</div>
|
</div>
|
||||||
<LocalIcon name="download" size="18" />
|
<span class="pending-download-icon" aria-hidden="true">
|
||||||
|
<LocalIcon name="download" size="18" />
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -515,6 +515,9 @@ a {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
background: var(--input-bg);
|
background: var(--input-bg);
|
||||||
@@ -534,25 +537,39 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pending-download-copy {
|
.pending-download-copy {
|
||||||
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pending-download-copy strong {
|
.pending-download-copy strong {
|
||||||
display: block;
|
display: -webkit-box;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: normal;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
line-height: 1.35;
|
||||||
|
max-height: calc(1.35em * 2);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pending-download-copy p {
|
.pending-download-copy p {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pending-download-icon {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
input.room-code,
|
input.room-code,
|
||||||
.text-input-group input,
|
.text-input-group input,
|
||||||
.text-input-group textarea {
|
.text-input-group textarea {
|
||||||
@@ -832,6 +849,9 @@ input.room-code::placeholder {
|
|||||||
|
|
||||||
.batch-item {
|
.batch-item {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
background: var(--item-bg);
|
background: var(--item-bg);
|
||||||
border: 1px solid var(--item-border);
|
border: 1px solid var(--item-border);
|
||||||
@@ -854,11 +874,14 @@ body[data-theme="dark"] .batch-item:hover {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
min-width: 0;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-info-left {
|
.file-info-left {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
max-width: 60%;
|
max-width: 60%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -867,6 +890,7 @@ body[data-theme="dark"] .batch-item:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-icon-wrapper {
|
.file-icon-wrapper {
|
||||||
|
flex: none;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -878,16 +902,26 @@ body[data-theme="dark"] .batch-item:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-name {
|
.file-name {
|
||||||
|
display: -webkit-box;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: normal;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
line-height: 1.35;
|
||||||
|
max-height: calc(1.35em * 2);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-info-right {
|
.file-info-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: none;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1446,8 +1480,16 @@ body[data-theme="dark"] .batch-item:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-info-right {
|
.file-info-right {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
row-gap: 8px;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-status {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
|
|||||||
Reference in New Issue
Block a user