feat: opaque session、安装/发帖 SSR 与最小 Admin 后台
浏览器登录改为 DB sessions(可吊销);敏感词与 OIDC PEM 入 settings; 落地安装向导、注册发帖与 /admin 仪表盘/板块/审核/设置。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
124
routers/install/install.go
Normal file
124
routers/install/install.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package install
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.iioio.com/freefire/jiang13-forum/modules/webctx"
|
||||
"git.iioio.com/freefire/jiang13-forum/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Deps 安装依赖
|
||||
type Deps struct {
|
||||
DataDir string
|
||||
JWTSecret string
|
||||
Auth *services.AuthService
|
||||
Settings *services.ForumSettingsService
|
||||
}
|
||||
|
||||
type pageData struct {
|
||||
Title string
|
||||
Description string
|
||||
SiteName string
|
||||
Slogan string
|
||||
LogoMark string
|
||||
CSRF string
|
||||
Error string
|
||||
Flash string
|
||||
AdminUsername string
|
||||
AdminEmail string
|
||||
AdminNickname string
|
||||
}
|
||||
|
||||
// Register 未安装时的路由(仅 /install)
|
||||
func Register(r *gin.Engine, deps Deps) {
|
||||
r.GET("/install", deps.Get)
|
||||
r.POST("/install", deps.Post)
|
||||
}
|
||||
|
||||
// Get 安装页
|
||||
func (d Deps) Get(c *gin.Context) {
|
||||
if services.IsInstalled(d.DataDir) {
|
||||
c.Redirect(http.StatusFound, "/")
|
||||
return
|
||||
}
|
||||
ctx := webctx.New(c, d.JWTSecret)
|
||||
brand := d.Settings.SiteBranding()
|
||||
name := brand.Name
|
||||
if name == "" {
|
||||
name = "姜十三论坛"
|
||||
}
|
||||
ctx.HTML(http.StatusOK, "install", pageData{
|
||||
Title: "安装 · " + name, SiteName: name, LogoMark: "姜",
|
||||
CSRF: ctx.EnsureCSRF(), AdminUsername: "admin",
|
||||
})
|
||||
}
|
||||
|
||||
// Post 提交安装
|
||||
func (d Deps) Post(c *gin.Context) {
|
||||
if services.IsInstalled(d.DataDir) {
|
||||
c.Redirect(http.StatusSeeOther, "/")
|
||||
return
|
||||
}
|
||||
ctx := webctx.New(c, d.JWTSecret)
|
||||
brand := d.Settings.SiteBranding()
|
||||
siteName := strings.TrimSpace(c.PostForm("site_name"))
|
||||
if siteName == "" {
|
||||
siteName = "姜十三论坛"
|
||||
}
|
||||
data := pageData{
|
||||
Title: "安装 · " + siteName, SiteName: siteName, LogoMark: "姜",
|
||||
CSRF: ctx.EnsureCSRF(),
|
||||
AdminUsername: strings.TrimSpace(c.PostForm("admin_username")),
|
||||
AdminEmail: strings.TrimSpace(c.PostForm("admin_email")),
|
||||
AdminNickname: strings.TrimSpace(c.PostForm("admin_nickname")),
|
||||
}
|
||||
if !ctx.CheckCSRF() {
|
||||
data.Error = "无效请求,请重试"
|
||||
ctx.HTML(http.StatusBadRequest, "install", data)
|
||||
return
|
||||
}
|
||||
pass := c.PostForm("admin_password")
|
||||
pass2 := c.PostForm("admin_password2")
|
||||
if pass != pass2 {
|
||||
data.Error = "两次密码不一致"
|
||||
ctx.HTML(http.StatusBadRequest, "install", data)
|
||||
return
|
||||
}
|
||||
if _, err := d.Auth.CreateAdmin(data.AdminUsername, pass, data.AdminNickname, data.AdminEmail); err != nil {
|
||||
data.Error = err.Error()
|
||||
ctx.HTML(http.StatusBadRequest, "install", data)
|
||||
return
|
||||
}
|
||||
b := d.Settings.SiteBranding()
|
||||
b.Name = siteName
|
||||
_ = d.Settings.UpdateSiteBranding(b)
|
||||
if err := services.WriteInstallLock(d.DataDir); err != nil {
|
||||
data.Error = "写入安装锁失败: " + err.Error()
|
||||
ctx.HTML(http.StatusInternalServerError, "install", data)
|
||||
return
|
||||
}
|
||||
ctx.HTML(http.StatusOK, "post-install", pageData{
|
||||
Title: "安装完成", SiteName: siteName, LogoMark: "姜",
|
||||
})
|
||||
_ = brand
|
||||
}
|
||||
|
||||
// Guard 未安装则只允许 install / assets / health
|
||||
func Guard(dataDir string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if services.IsInstalled(dataDir) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
path := c.Request.URL.Path
|
||||
if path == "/install" || path == "/health" ||
|
||||
strings.HasPrefix(path, "/ssr-assets/") {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
c.Redirect(http.StatusFound, "/install")
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user