Skip to content

Commit 32bec94

Browse files
committed
feat: 新增新用户引导页
1 parent ded6e31 commit 32bec94

File tree

9 files changed

+194
-15
lines changed

9 files changed

+194
-15
lines changed

app/client-layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import { Footer } from "@/components/layout/footer";
1111
export function ClientLayout({ children }: { children: React.ReactNode }) {
1212
const pathname = usePathname();
1313
const isSimpleLayout = pathname === '/login' || pathname === '/login/' ||
14-
pathname === '/oauth-error' || pathname === '/oauth-error/';
14+
pathname === '/oauth-error' || pathname === '/oauth-error/' ||
15+
pathname === '/setup-guide' || pathname === '/setup-guide/';
1516

1617
return (
1718
<AuthProvider>

app/components/route-guard.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ interface RouteGuardProps {
1515
// 公开路由列表(不需要身份验证)
1616
// 由于 next.config.js 中设置了 `trailingSlash: true`,导出后路径可能变成 `/login/`
1717
// 为兼容两种情况,统一在比较前去除末尾斜杠,再进行匹配
18-
const RAW_PUBLIC_ROUTES = ['/login', '/oauth-error'];
18+
const RAW_PUBLIC_ROUTES = ['/login', '/oauth-error', '/setup-guide'];
1919

2020
/**
2121
* 规范化路径,去除末尾斜杠(根路径 `/` 除外)
@@ -44,10 +44,12 @@ export function RouteGuard({ children }: RouteGuardProps) {
4444
const isPublicRoute = PUBLIC_ROUTES.includes(normalizePath(pathname));
4545

4646
console.log('🛡️ RouteGuard 路由检查', {
47+
pathname,
4748
isPublicRoute,
4849
hasUser: !!user,
50+
isSetupGuide: pathname === '/setup-guide',
4951
action: !user && !isPublicRoute ? '重定向到登录页' :
50-
user && isPublicRoute ? '重定向到仪表盘' : '无需重定向'
52+
user && isPublicRoute && pathname !== '/setup-guide' ? '重定向到仪表盘' : '无需重定向'
5153
});
5254

5355
// 添加小延迟,避免与其他导航操作冲突
@@ -56,8 +58,9 @@ export function RouteGuard({ children }: RouteGuardProps) {
5658
// 用户未登录且访问私有路由,重定向到登录页
5759
console.log('🔒 执行重定向:用户未登录,前往登录页');
5860
router.replace('/login');
59-
} else if (user && isPublicRoute) {
61+
} else if (user && isPublicRoute && pathname !== '/setup-guide') {
6062
// 用户已登录但访问公开路由(如登录页),重定向到仪表盘
63+
// 但是允许已登录用户访问引导页面
6164
console.log('👤 执行重定向:用户已登录,前往仪表盘');
6265
router.replace('/dashboard');
6366
}
@@ -83,7 +86,7 @@ export function RouteGuard({ children }: RouteGuardProps) {
8386

8487
// 检查是否应该显示内容
8588
const isPublicRoute = PUBLIC_ROUTES.includes(normalizePath(pathname));
86-
const shouldShowContent = (user && !isPublicRoute) || (!user && isPublicRoute);
89+
const shouldShowContent = (user && !isPublicRoute) || (!user && isPublicRoute) || (user && pathname === '/setup-guide');
8790

8891
if (!shouldShowContent) {
8992
// 正在重定向中,显示加载状态

app/login/page.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,22 @@ export default function LoginPage() {
114114
localStorage.setItem('nodepass.user', JSON.stringify(loginUser));
115115
}
116116

117-
console.log('🚀 重定向到仪表盘');
118-
router.push('/dashboard');
117+
// 检查是否是默认账号密码,如果是则跳转到引导页面
118+
console.log('🔍 检查默认凭据状态', {
119+
isDefaultCredentials: result.isDefaultCredentials,
120+
type: typeof result.isDefaultCredentials
121+
});
122+
123+
if (result.isDefaultCredentials === true) {
124+
console.log('🔧 检测到默认凭据,重定向到安全设置引导页');
125+
console.log('🔍 开始跳转到 /setup-guide');
126+
127+
// 直接使用 window.location 进行跳转,避免与路由守卫冲突
128+
window.location.href = '/setup-guide';
129+
} else {
130+
console.log('🚀 重定向到仪表盘');
131+
router.push('/dashboard');
132+
}
119133
} else {
120134
console.error('❌ 登录失败', result);
121135
setError(result.error || '登录失败');

go.mod

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,31 @@ require (
88
github.com/google/uuid v1.6.0
99
github.com/gorilla/mux v1.8.1
1010
github.com/mattn/go-ieproxy v0.0.12
11-
github.com/mattn/go-sqlite3 v1.14.22
1211
github.com/r3labs/sse/v2 v2.10.0
1312
github.com/sirupsen/logrus v1.9.3
1413
golang.org/x/crypto v0.38.0
1514
gorm.io/driver/sqlite v1.5.4
1615
gorm.io/gorm v1.25.5
16+
modernc.org/sqlite v1.33.1
1717
)
1818

1919
require (
20+
github.com/dustin/go-humanize v1.0.1 // indirect
21+
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
2022
github.com/jinzhu/inflection v1.0.0 // indirect
2123
github.com/jinzhu/now v1.1.5 // indirect
22-
github.com/shirou/gopsutil/v3 v3.20.10 // indirect
24+
github.com/mattn/go-isatty v0.0.20 // indirect
25+
github.com/mattn/go-sqlite3 v1.14.17 // indirect
26+
github.com/ncruces/go-strftime v0.1.9 // indirect
27+
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
2328
golang.org/x/net v0.40.0 // indirect
2429
golang.org/x/sys v0.33.0 // indirect
2530
golang.org/x/text v0.25.0 // indirect
2631
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
32+
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
33+
modernc.org/libc v1.55.3 // indirect
34+
modernc.org/mathutil v1.6.0 // indirect
35+
modernc.org/memory v1.8.0 // indirect
36+
modernc.org/strutil v1.2.0 // indirect
37+
modernc.org/token v1.1.0 // indirect
2738
)

internal/api/auth.go

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,19 @@ func (h *AuthHandler) HandleLogin(w http.ResponseWriter, r *http.Request) {
102102
SameSite: http.SameSiteLaxMode,
103103
})
104104

105+
// 检查是否是默认账号密码
106+
isDefaultCredentials := h.authService.IsDefaultCredentials()
107+
108+
// 调试日志
109+
fmt.Printf("🔍 登录成功,检查默认凭据状态: %v\n", isDefaultCredentials)
110+
105111
// 返回成功响应
106-
json.NewEncoder(w).Encode(auth.LoginResponse{
107-
Success: true,
108-
Message: "登录成功",
109-
})
112+
response := map[string]interface{}{
113+
"success": true,
114+
"message": "登录成功",
115+
"isDefaultCredentials": isDefaultCredentials,
116+
}
117+
json.NewEncoder(w).Encode(response)
110118
}
111119

112120
// HandleLogout 处理登出请求
@@ -237,6 +245,13 @@ type UsernameChangeRequest struct {
237245
NewUsername string `json:"newUsername"`
238246
}
239247

248+
// SecurityUpdateRequest 安全设置更新请求体(用户名+密码)
249+
type SecurityUpdateRequest struct {
250+
CurrentPassword string `json:"currentPassword"`
251+
NewUsername string `json:"newUsername"`
252+
NewPassword string `json:"newPassword"`
253+
}
254+
240255
// HandleChangePassword 修改密码
241256
func (h *AuthHandler) HandleChangePassword(w http.ResponseWriter, r *http.Request) {
242257
if r.Method != http.MethodPost {
@@ -338,6 +353,57 @@ func (h *AuthHandler) HandleChangeUsername(w http.ResponseWriter, r *http.Reques
338353
json.NewEncoder(w).Encode(map[string]interface{}{"success": true, "message": msg})
339354
}
340355

356+
// HandleUpdateSecurity 同时修改用户名和密码
357+
func (h *AuthHandler) HandleUpdateSecurity(w http.ResponseWriter, r *http.Request) {
358+
if r.Method != http.MethodPost {
359+
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
360+
return
361+
}
362+
363+
// 获取 session cookie
364+
cookie, err := r.Cookie("session")
365+
if err != nil {
366+
w.WriteHeader(http.StatusUnauthorized)
367+
json.NewEncoder(w).Encode(map[string]interface{}{"success": false, "message": "未登录"})
368+
return
369+
}
370+
371+
if !h.authService.ValidateSession(cookie.Value) {
372+
w.WriteHeader(http.StatusUnauthorized)
373+
json.NewEncoder(w).Encode(map[string]interface{}{"success": false, "message": "会话无效"})
374+
return
375+
}
376+
377+
sess, ok := h.authService.GetSession(cookie.Value)
378+
if !ok {
379+
w.WriteHeader(http.StatusUnauthorized)
380+
json.NewEncoder(w).Encode(map[string]interface{}{"success": false, "message": "会话无效"})
381+
return
382+
}
383+
384+
var req SecurityUpdateRequest
385+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
386+
w.WriteHeader(http.StatusBadRequest)
387+
json.NewEncoder(w).Encode(map[string]interface{}{"success": false, "message": "无效请求体"})
388+
return
389+
}
390+
391+
if req.CurrentPassword == "" || req.NewUsername == "" || req.NewPassword == "" {
392+
w.WriteHeader(http.StatusBadRequest)
393+
json.NewEncoder(w).Encode(map[string]interface{}{"success": false, "message": "缺少必填字段"})
394+
return
395+
}
396+
397+
ok2, msg := h.authService.UpdateSecurity(sess.Username, req.CurrentPassword, req.NewUsername, req.NewPassword)
398+
if !ok2 {
399+
w.WriteHeader(http.StatusBadRequest)
400+
json.NewEncoder(w).Encode(map[string]interface{}{"success": false, "message": msg})
401+
return
402+
}
403+
404+
json.NewEncoder(w).Encode(map[string]interface{}{"success": true, "message": msg})
405+
}
406+
341407
// HandleOAuth2Callback 处理第三方 OAuth2 回调
342408
//
343409
// 目前仅作为占位实现,记录回调信息并返回成功响应。

internal/api/routes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ func (r *Router) registerRoutes() {
106106
r.router.HandleFunc("/api/auth/init", r.authHandler.HandleInitSystem).Methods("POST")
107107
r.router.HandleFunc("/api/auth/change-password", r.authHandler.HandleChangePassword).Methods("POST")
108108
r.router.HandleFunc("/api/auth/change-username", r.authHandler.HandleChangeUsername).Methods("POST")
109+
r.router.HandleFunc("/api/auth/update-security", r.authHandler.HandleUpdateSecurity).Methods("POST")
109110
r.router.HandleFunc("/api/auth/oauth2", r.authHandler.HandleOAuth2Provider).Methods("GET")
110111

111112
// OAuth2 回调

internal/auth/models.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,9 @@ const (
3636
ConfigKeyAdminUsername = "admin_username"
3737
ConfigKeyAdminPassword = "admin_password_hash"
3838
)
39+
40+
// 默认账号密码常量
41+
const (
42+
DefaultAdminUsername = "nodepass"
43+
DefaultAdminPassword = "Np123456"
44+
)

internal/auth/service.go

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,25 @@ func (s *Service) IsSystemInitialized() bool {
127127
return value == "true"
128128
}
129129

130+
// IsDefaultCredentials 检查当前账号密码是否是默认的
131+
func (s *Service) IsDefaultCredentials() bool {
132+
storedUsername, _ := s.GetSystemConfig(ConfigKeyAdminUsername)
133+
storedPasswordHash, _ := s.GetSystemConfig(ConfigKeyAdminPassword)
134+
135+
fmt.Printf("🔍 检查默认凭据: username=%s (expected=%s), hasHash=%v\n",
136+
storedUsername, DefaultAdminUsername, storedPasswordHash != "")
137+
138+
if storedUsername != DefaultAdminUsername {
139+
fmt.Printf("🔍 用户名不匹配,非默认凭据\n")
140+
return false
141+
}
142+
143+
// 验证密码是否是默认密码
144+
isDefaultPassword := s.VerifyPassword(DefaultAdminPassword, storedPasswordHash)
145+
fmt.Printf("🔍 密码验证结果: %v\n", isDefaultPassword)
146+
return isDefaultPassword
147+
}
148+
130149
// AuthenticateUser 用户登录验证
131150
func (s *Service) AuthenticateUser(username, password string) bool {
132151
storedUsername, _ := s.GetSystemConfig(ConfigKeyAdminUsername)
@@ -277,8 +296,8 @@ func (s *Service) InitializeSystem() (string, string, error) {
277296
return "", "", errors.New("系统已初始化")
278297
}
279298

280-
username := "nodepass"
281-
password := generateRandomPassword(12)
299+
username := DefaultAdminUsername
300+
password := DefaultAdminPassword
282301

283302
passwordHash, err := s.HashPassword(password)
284303
if err != nil {
@@ -366,6 +385,11 @@ func (s *Service) ChangePassword(username, currentPassword, newPassword string)
366385
return false, "当前密码不正确"
367386
}
368387

388+
// 验证新密码不能与默认密码相同
389+
if newPassword == DefaultAdminPassword {
390+
return false, "新密码不能与默认密码相同,请设置一个安全的密码"
391+
}
392+
369393
// 加密新密码
370394
hash, err := s.HashPassword(newPassword)
371395
if err != nil {
@@ -389,6 +413,8 @@ func (s *Service) ChangeUsername(currentUsername, newUsername string) (bool, str
389413
return false, "当前用户名不正确"
390414
}
391415

416+
// 允许设置任何用户名,包括默认用户名
417+
392418
// 更新系统配置中的用户名
393419
if err := s.SetSystemConfig(ConfigKeyAdminUsername, newUsername); err != nil {
394420
return false, "更新用户名失败"
@@ -412,6 +438,56 @@ func (s *Service) ChangeUsername(currentUsername, newUsername string) (bool, str
412438
return true, "用户名修改成功"
413439
}
414440

441+
// UpdateSecurity 同时修改用户名和密码
442+
func (s *Service) UpdateSecurity(currentUsername, currentPassword, newUsername, newPassword string) (bool, string) {
443+
// 验证当前用户身份
444+
if !s.AuthenticateUser(currentUsername, currentPassword) {
445+
return false, "当前密码不正确"
446+
}
447+
448+
// 验证新密码不能与默认密码相同
449+
if newPassword == DefaultAdminPassword {
450+
return false, "新密码不能与默认密码相同,请设置一个安全的密码"
451+
}
452+
453+
// 允许设置任何用户名,包括默认用户名
454+
455+
// 加密新密码
456+
hash, err := s.HashPassword(newPassword)
457+
if err != nil {
458+
return false, "密码加密失败"
459+
}
460+
461+
// 更新用户名
462+
if err := s.SetSystemConfig(ConfigKeyAdminUsername, newUsername); err != nil {
463+
return false, "更新用户名失败"
464+
}
465+
466+
// 更新密码
467+
if err := s.SetSystemConfig(ConfigKeyAdminPassword, hash); err != nil {
468+
// 如果密码更新失败,回滚用户名
469+
s.SetSystemConfig(ConfigKeyAdminUsername, currentUsername)
470+
return false, "更新密码失败"
471+
}
472+
473+
// 更新数据库中的会话记录
474+
s.db.Model(&models.UserSession{}).Where("username = ?", currentUsername).Update("username", newUsername)
475+
476+
// 更新缓存中的会话
477+
sessionCache.Range(func(key, value interface{}) bool {
478+
sess := value.(Session)
479+
if sess.Username == currentUsername {
480+
sess.Username = newUsername
481+
sessionCache.Store(key, sess)
482+
}
483+
return true
484+
})
485+
486+
// 使所有现有 Session 失效
487+
s.invalidateAllSessions()
488+
return true, "账号信息修改成功"
489+
}
490+
415491
// ResetAdminPassword 重置管理员密码并返回新密码
416492
func (s *Service) ResetAdminPassword() (string, string, error) {
417493
// 确认系统已初始化

internal/db/db.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"gorm.io/driver/sqlite"
1313
"gorm.io/gorm"
1414
"gorm.io/gorm/logger"
15+
_ "modernc.org/sqlite"
1516
)
1617

1718
var (

0 commit comments

Comments
 (0)