mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 15:25:50 +00:00
修复朋友圈封面信息被错误解析的问题;解决了一些安全问题
This commit is contained in:
@@ -312,7 +312,7 @@ function App() {
|
||||
const checkLock = async () => {
|
||||
// 并行获取配置,减少等待
|
||||
const [enabled, useHello] = await Promise.all([
|
||||
configService.getAuthEnabled(),
|
||||
window.electronAPI.auth.verifyEnabled(),
|
||||
configService.getAuthUseHello()
|
||||
])
|
||||
|
||||
|
||||
@@ -105,9 +105,19 @@ export default function LockScreen({ onUnlock, avatar, useHello = false }: LockS
|
||||
|
||||
try {
|
||||
const storedHash = await configService.getAuthPassword()
|
||||
|
||||
// 兜底:如果没有设置过密码,直接放行并关闭应用锁
|
||||
if (!storedHash) {
|
||||
await configService.setAuthEnabled(false)
|
||||
handleUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
const inputHash = await sha256(password)
|
||||
|
||||
if (inputHash === storedHash) {
|
||||
// 解锁成功,重新写入 authEnabled 以修复可能被篡改的签名
|
||||
await configService.setAuthEnabled(true)
|
||||
handleUnlock()
|
||||
} else {
|
||||
setError('密码错误')
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'
|
||||
import { NavLink, useLocation } from 'react-router-dom'
|
||||
import { Home, MessageSquare, BarChart3, Users, FileText, Database, Settings, ChevronLeft, ChevronRight, Download, Aperture, UserCircle, Lock } from 'lucide-react'
|
||||
import { useAppStore } from '../stores/appStore'
|
||||
import * as configService from '../services/config'
|
||||
|
||||
import './Sidebar.scss'
|
||||
|
||||
function Sidebar() {
|
||||
@@ -12,7 +12,7 @@ function Sidebar() {
|
||||
const setLocked = useAppStore(state => state.setLocked)
|
||||
|
||||
useEffect(() => {
|
||||
configService.getAuthEnabled().then(setAuthEnabled)
|
||||
window.electronAPI.auth.verifyEnabled().then(setAuthEnabled)
|
||||
}, [])
|
||||
|
||||
const isActive = (path: string) => {
|
||||
|
||||
@@ -21,6 +21,7 @@ interface SnsMedia {
|
||||
|
||||
interface SnsMediaGridProps {
|
||||
mediaList: SnsMedia[]
|
||||
postType?: number
|
||||
onPreview: (src: string, isVideo?: boolean, liveVideoPath?: string) => void
|
||||
onMediaDeleted?: () => void
|
||||
}
|
||||
@@ -80,7 +81,7 @@ const extractVideoFrame = async (videoPath: string): Promise<string> => {
|
||||
})
|
||||
}
|
||||
|
||||
const MediaItem = ({ media, onPreview, onMediaDeleted }: { media: SnsMedia; onPreview: (src: string, isVideo?: boolean, liveVideoPath?: string) => void; onMediaDeleted?: () => void }) => {
|
||||
const MediaItem = ({ media, postType, onPreview, onMediaDeleted }: { media: SnsMedia; postType?: number; onPreview: (src: string, isVideo?: boolean, liveVideoPath?: string) => void; onMediaDeleted?: () => void }) => {
|
||||
const [error, setError] = useState(false)
|
||||
const [deleted, setDeleted] = useState(false)
|
||||
const [loading, setLoading] = useState(true)
|
||||
@@ -96,6 +97,8 @@ const MediaItem = ({ media, onPreview, onMediaDeleted }: { media: SnsMedia; onPr
|
||||
const isVideo = isSnsVideoUrl(media.url)
|
||||
const isLive = !!media.livePhoto
|
||||
const targetUrl = media.thumb || media.url
|
||||
// type 7 的朋友圈媒体不需要解密,直接使用原始 URL
|
||||
const skipDecrypt = postType === 7
|
||||
|
||||
// 视频重试:失败时重试最多2次,耗尽才标记删除
|
||||
const videoRetryOrDelete = () => {
|
||||
@@ -119,7 +122,7 @@ const MediaItem = ({ media, onPreview, onMediaDeleted }: { media: SnsMedia; onPr
|
||||
// For images, we proxy to get the local path/base64
|
||||
const result = await window.electronAPI.sns.proxyImage({
|
||||
url: targetUrl,
|
||||
key: media.key
|
||||
key: skipDecrypt ? undefined : media.key
|
||||
})
|
||||
if (cancelled) return
|
||||
|
||||
@@ -134,7 +137,7 @@ const MediaItem = ({ media, onPreview, onMediaDeleted }: { media: SnsMedia; onPr
|
||||
if (isLive && media.livePhoto?.url) {
|
||||
window.electronAPI.sns.proxyImage({
|
||||
url: media.livePhoto.url,
|
||||
key: media.livePhoto.key || media.key
|
||||
key: skipDecrypt ? undefined : (media.livePhoto.key || media.key)
|
||||
}).then((res: any) => {
|
||||
if (!cancelled && res.success && res.videoPath) {
|
||||
setLiveVideoPath(`file://${res.videoPath.replace(/\\/g, '/')}`)
|
||||
@@ -150,7 +153,7 @@ const MediaItem = ({ media, onPreview, onMediaDeleted }: { media: SnsMedia; onPr
|
||||
// Usually we need to call proxyImage with the video URL to decrypt it to cache
|
||||
const result = await window.electronAPI.sns.proxyImage({
|
||||
url: media.url,
|
||||
key: media.key
|
||||
key: skipDecrypt ? undefined : media.key
|
||||
})
|
||||
|
||||
if (cancelled) return
|
||||
@@ -201,7 +204,7 @@ const MediaItem = ({ media, onPreview, onMediaDeleted }: { media: SnsMedia; onPr
|
||||
try {
|
||||
const res = await window.electronAPI.sns.proxyImage({
|
||||
url: media.url,
|
||||
key: media.key
|
||||
key: skipDecrypt ? undefined : media.key
|
||||
})
|
||||
if (res.success && res.videoPath) {
|
||||
const local = `file://${res.videoPath.replace(/\\/g, '/')}`
|
||||
@@ -229,7 +232,7 @@ const MediaItem = ({ media, onPreview, onMediaDeleted }: { media: SnsMedia; onPr
|
||||
try {
|
||||
const result = await window.electronAPI.sns.proxyImage({
|
||||
url: media.url,
|
||||
key: media.key
|
||||
key: skipDecrypt ? undefined : media.key
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
@@ -334,7 +337,7 @@ const MediaItem = ({ media, onPreview, onMediaDeleted }: { media: SnsMedia; onPr
|
||||
)
|
||||
}
|
||||
|
||||
export const SnsMediaGrid: React.FC<SnsMediaGridProps> = ({ mediaList, onPreview, onMediaDeleted }) => {
|
||||
export const SnsMediaGrid: React.FC<SnsMediaGridProps> = ({ mediaList, postType, onPreview, onMediaDeleted }) => {
|
||||
if (!mediaList || mediaList.length === 0) return null
|
||||
|
||||
const count = mediaList.length
|
||||
@@ -350,7 +353,7 @@ export const SnsMediaGrid: React.FC<SnsMediaGridProps> = ({ mediaList, onPreview
|
||||
return (
|
||||
<div className={`sns-media-grid ${gridClass}`}>
|
||||
{mediaList.map((media, idx) => (
|
||||
<MediaItem key={idx} media={media} onPreview={onPreview} onMediaDeleted={onMediaDeleted} />
|
||||
<MediaItem key={idx} media={media} postType={postType} onPreview={onPreview} onMediaDeleted={onMediaDeleted} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -264,7 +264,7 @@ export const SnsPostItem: React.FC<SnsPostItemProps> = ({ post, onPreview, onDeb
|
||||
|
||||
{showMediaGrid && (
|
||||
<div className="post-media-container">
|
||||
<SnsMediaGrid mediaList={post.media} onPreview={onPreview} onMediaDeleted={[1, 54].includes(post.type ?? 0) ? () => setMediaDeleted(true) : undefined} />
|
||||
<SnsMediaGrid mediaList={post.media} postType={post.type} onPreview={onPreview} onMediaDeleted={[1, 54].includes(post.type ?? 0) ? () => setMediaDeleted(true) : undefined} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -279,7 +279,7 @@ function SettingsPage() {
|
||||
const savedNotificationFilterMode = await configService.getNotificationFilterMode()
|
||||
const savedNotificationFilterList = await configService.getNotificationFilterList()
|
||||
|
||||
const savedAuthEnabled = await configService.getAuthEnabled()
|
||||
const savedAuthEnabled = await window.electronAPI.auth.verifyEnabled()
|
||||
const savedAuthUseHello = await configService.getAuthUseHello()
|
||||
setAuthEnabled(savedAuthEnabled)
|
||||
setAuthUseHello(savedAuthUseHello)
|
||||
@@ -2046,6 +2046,14 @@ function SettingsPage() {
|
||||
checked={authEnabled}
|
||||
onChange={async (e) => {
|
||||
const enabled = e.target.checked
|
||||
if (enabled) {
|
||||
// 检查是否已设置密码,未设置则阻止开启
|
||||
const storedHash = await configService.getAuthPassword()
|
||||
if (!storedHash) {
|
||||
showMessage('请先设置密码再启用应用锁', false)
|
||||
return
|
||||
}
|
||||
}
|
||||
setAuthEnabled(enabled)
|
||||
await configService.setAuthEnabled(enabled)
|
||||
}}
|
||||
|
||||
4
src/types/electron.d.ts
vendored
4
src/types/electron.d.ts
vendored
@@ -19,6 +19,10 @@ export interface ElectronAPI {
|
||||
set: (key: string, value: unknown) => Promise<void>
|
||||
clear: () => Promise<boolean>
|
||||
}
|
||||
auth: {
|
||||
hello: (message?: string) => Promise<{ success: boolean; error?: string }>
|
||||
verifyEnabled: () => Promise<boolean>
|
||||
}
|
||||
dialog: {
|
||||
openFile: (options?: Electron.OpenDialogOptions) => Promise<Electron.OpenDialogReturnValue>
|
||||
openDirectory: (options?: Electron.OpenDialogOptions) => Promise<Electron.OpenDialogReturnValue>
|
||||
|
||||
1
src/vite-env.d.ts
vendored
1
src/vite-env.d.ts
vendored
@@ -5,6 +5,7 @@ interface Window {
|
||||
// ... other methods ...
|
||||
auth: {
|
||||
hello: (message?: string) => Promise<{ success: boolean; error?: string }>
|
||||
verifyEnabled: () => Promise<boolean>
|
||||
}
|
||||
// For brevity, using 'any' for other parts or properly importing types if available.
|
||||
// In a real scenario, you'd likely want to keep the full interface definition consistent with preload.ts
|
||||
|
||||
Reference in New Issue
Block a user