fix(P2): rand.Read错误处理 + JWT随机密钥 + 密码环境变量化 + 前端路由守卫
This commit is contained in:
17
.env.example
Normal file
17
.env.example
Normal file
@@ -0,0 +1,17 @@
|
||||
# feedsystem_video_go 环境变量模板
|
||||
# 复制此文件为 .env 后修改实际值
|
||||
# .env 已被 .gitignore 忽略,不会提交到仓库
|
||||
|
||||
# MySQL
|
||||
MYSQL_ROOT_PASSWORD=123456
|
||||
MYSQL_DATABASE=feedsystem
|
||||
|
||||
# Redis
|
||||
REDIS_PASSWORD=123456
|
||||
|
||||
# RabbitMQ
|
||||
RABBITMQ_USER=admin
|
||||
RABBITMQ_PASS=password123
|
||||
|
||||
# JWT (生产环境务必修改为随机强密钥)
|
||||
JWT_SECRET=change-me-in-production
|
||||
@@ -2,7 +2,10 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@@ -12,7 +15,13 @@ import (
|
||||
func jwtSecret() []byte {
|
||||
secret := os.Getenv("JWT_SECRET")
|
||||
if secret == "" {
|
||||
secret = "change-me-in-env"
|
||||
b := make([]byte, 32)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
log.Printf("FATAL: cannot generate JWT secret: %v", err)
|
||||
return []byte("fallback-unsafe-key-change-me")
|
||||
}
|
||||
secret = hex.EncodeToString(b)
|
||||
log.Printf("WARNING: JWT_SECRET not set, generated random key. All tokens invalid on restart.")
|
||||
}
|
||||
return []byte(secret)
|
||||
}
|
||||
|
||||
@@ -93,7 +93,12 @@ func (vh *VideoHandler) UploadVideo(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
filename := randHex(16) + ext
|
||||
filename, err := randHex(16)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate filename"})
|
||||
return
|
||||
}
|
||||
filename = filename + ext
|
||||
absPath := filepath.Join(absDir, filename)
|
||||
|
||||
if err := c.SaveUploadedFile(f, absPath); err != nil {
|
||||
@@ -145,7 +150,12 @@ func (vh *VideoHandler) UploadCover(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
filename := randHex(16) + ext
|
||||
filename, err := randHex(16)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate filename"})
|
||||
return
|
||||
}
|
||||
filename = filename + ext
|
||||
absPath := filepath.Join(absDir, filename)
|
||||
|
||||
if err := c.SaveUploadedFile(f, absPath); err != nil {
|
||||
@@ -161,10 +171,12 @@ func (vh *VideoHandler) UploadCover(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func randHex(n int) string {
|
||||
func randHex(n int) (string, error) {
|
||||
b := make([]byte, n)
|
||||
_, _ = rand.Read(b)
|
||||
return hex.EncodeToString(b)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", fmt.Errorf("rand.Read: %w", err)
|
||||
}
|
||||
return hex.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
func buildAbsoluteURL(c *gin.Context, p string) string {
|
||||
|
||||
@@ -5,8 +5,8 @@ services:
|
||||
image: mysql:8.0
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: "123456"
|
||||
MYSQL_DATABASE: "feedsystem"
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-123456}
|
||||
MYSQL_DATABASE: ${MYSQL_DATABASE:-feedsystem}
|
||||
TZ: "Asia/Shanghai"
|
||||
ports:
|
||||
- "3307:3306"
|
||||
@@ -25,7 +25,7 @@ services:
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
restart: always
|
||||
command: ["redis-server", "--appendonly", "yes", "--requirepass", "123456"]
|
||||
command: ["redis-server", "--appendonly", "yes", "--requirepass", "${REDIS_PASSWORD:-123456}"]
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
@@ -43,8 +43,8 @@ services:
|
||||
- "5672:5672"
|
||||
- "15672:15672"
|
||||
environment:
|
||||
RABBITMQ_DEFAULT_USER: admin
|
||||
RABBITMQ_DEFAULT_PASS: password123
|
||||
RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER:-admin}
|
||||
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASS:-password123}
|
||||
volumes:
|
||||
- rabbitmq_data:/var/lib/rabbitmq
|
||||
healthcheck:
|
||||
|
||||
@@ -9,6 +9,7 @@ import ChangePasswordView from '../views/ChangePasswordView.vue'
|
||||
import RegisterView from '../views/RegisterView.vue'
|
||||
import SettingsView from '../views/SettingsView.vue'
|
||||
import UserProfileView from '../views/UserProfileView.vue'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
@@ -16,14 +17,23 @@ const router = createRouter({
|
||||
{ path: '/', name: 'home', component: HomeView },
|
||||
{ path: '/feed', redirect: '/' },
|
||||
{ path: '/hot', name: 'hot', component: HotView },
|
||||
{ path: '/video', name: 'video', component: VideoView },
|
||||
{ path: '/video', name: 'video', component: VideoView, meta: { requiresAuth: true } },
|
||||
{ path: '/video/:id', name: 'video-detail', component: VideoDetailView, props: true },
|
||||
{ path: '/account', name: 'account', component: AccountView },
|
||||
{ path: '/account/register', name: 'account-register', component: RegisterView },
|
||||
{ path: '/account/change-password', name: 'account-change-password', component: ChangePasswordView },
|
||||
{ path: '/settings', name: 'settings', component: SettingsView },
|
||||
{ path: '/settings', name: 'settings', component: SettingsView, meta: { requiresAuth: true } },
|
||||
{ path: '/u/:id', name: 'user-profile', component: UserProfileView, props: true },
|
||||
],
|
||||
})
|
||||
|
||||
router.beforeEach((to, _from, next) => {
|
||||
const auth = useAuthStore()
|
||||
if (to.meta.requiresAuth && !auth.isLoggedIn) {
|
||||
next({ path: '/account', query: { redirect: to.fullPath } })
|
||||
return
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
Reference in New Issue
Block a user